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

@liberfi.io/react

v0.1.32

Published

React integration layer for Liberfi SDK

Downloads

3,175

Readme

@liberfi.io/react

React integration layer for the Liberfi SDK. This package provides a DexClientProvider for dependency injection, query hooks (powered by TanStack Query) for data fetching, and subscription hooks for real-time WebSocket updates. It is the primary package consumed by Liberfi UI applications and widgets.

Design Philosophy

  • Provider-based DIDexClientProvider injects IClient and ISubscribeClient instances via React context, keeping hooks decoupled from concrete implementations.
  • Factory-generated hookscreateQueryHook and createInfiniteQueryHook generate hooks from a declarative config, ensuring consistent API surface (each hook exports use*Query, *QueryKey, and fetch*).
  • No built-in side effects — Hooks do not trigger toasts, modals, or analytics. Errors are surfaced via TanStack Query state or onError callbacks, giving the caller full control.
  • Query key namespacing — All keys are prefixed with a configurable namespace (default "liberfi") to avoid collisions.

Installation

pnpm add @liberfi.io/react

Peer Dependencies

The consumer must provide:

| Package | Version | | ----------------------- | ------- | | react | >= 18 | | react-dom | >= 18 | | @tanstack/react-query | ^5.90.2 |

API Reference

Components

DexClientProvider

Provides the Dex client instances and configuration to all descendant hooks.

| Prop | Type | Default | Description | | ----------------- | ---------------------- | ----------- | --------------------------------------- | | client | API.IClient | (required) | REST/HTTP client instance. | | subscribeClient | API.ISubscribeClient | (required) | WebSocket subscription client instance. | | queryKeyPrefix | string | "liberfi" | Prefix for all TanStack Query keys. | | children | ReactNode | (required) | Child components. |

Hooks — Core

useDexClient

Returns the DexClientContextValue from the nearest DexClientProvider.

const { client, subscribeClient, queryKeyPrefix } = useDexClient();

Throws if called outside a DexClientProvider.

Hooks — Query (27 hooks)

Each query hook is generated by createQueryHook and exports three items:

  • use*Query(params, options?) — React hook returning UseQueryResult<TData, Error>.
  • *QueryKey(params, prefix?) — Builds the full query key array for cache operations.
  • fetch*(client, params) — Standalone fetch function for use outside React.

| Hook | Params | Return Data | | ------------------------------------- | ----------------------------------------------------------- | ------------------------- | | useTokenQuery | { chain, address } | Token | | useTokensQuery | { chain, addresses } | Token[] | | useSearchTokensQuery | SearchTokensOptions | SearchTokenCursorList | | useNewTokensQuery | { chain, ...GetTokenListOptions } | Token[] | | useTrendingTokensQuery | { chain, resolution, ...GetTokenListOptions } | Token[] | | useMigratedTokensQuery | { chain, ...GetTokenListOptions } | Token[] | | useFinalStretchTokensQuery | { chain, ...GetTokenListOptions } | Token[] | | useStockTokensQuery | { chain, ...GetTokenListOptions } | Token[] | | useSwapRouteQuery | SwapParams | SwapRoute | | useTokenCandlesQuery | { chain, address, resolution, ...GetTokenCandlesOptions } | TokenCandle[] | | useTokenMarketDataQuery | { chain, address } | TokenMarketData | | useTokenSecurityQuery | { chain, address } | TokenSecurity | | useTokenStatsQuery | { chain, address } | TokenStats | | useTokenHoldersQuery | { chain, address, ...CursorListOptions } | CursorList<TokenHolder> | | useTokenTradesQuery | { chain, address, ...GetTradesOptions } | CursorList<Trade> | | useTokenActivitiesQuery | { chain, address, ...GetActivitiesOptions } | CursorList<Activity> | | useWalletPnlQuery | { chain, address, resolution? } | WalletPnl | | useWalletPortfoliosQuery | { chain, address, ...paginationOptions } | WalletPortfolios | | useWalletPortfoliosByTokensQuery | { chain, address, tokenAddresses } | Portfolio[] | | useWalletPortfolioPnlsQuery | { chain, address, ...paginationOptions } | WalletPortfolioPnls | | useWalletPortfolioPnlsByTokensQuery | { chain, address, tokenAddresses } | PortfolioPnl[] | | useWalletTradesQuery | { chain, address, ...GetTradesOptions } | CursorList<Trade> | | useWalletActivitiesQuery | { chain, address, ...GetActivitiesOptions } | CursorList<Activity> | | useTxSuccessQuery | { chain, txHash, timeout? } | boolean | | usePresignedUploadUrlQuery | (none) | string |

Special hooks

| Hook | Type | Description | | ------------------------------------- | -------------- | ------------------------------------------------------------------------------------------- | | useSendTxMutation | Mutation | Sends a signed transaction. Returns UseMutationResult<SendTxResult, Error, SendTxParams>. | | useWalletPortfoliosInfiniteQuery | Infinite Query | Cursor-paginated wallet holdings with fetchNextPage(). | | useWalletPortfolioPnlsInfiniteQuery | Infinite Query | Cursor-paginated wallet PnLs with fetchNextPage(). |

Hooks — Subscription (14 hooks)

Each subscription hook manages WebSocket lifecycle automatically (subscribe on mount, unsubscribe on unmount or deps change).

Signature: use*Subscription(params, callback, options?)

| Hook | Params | Callback Data | | ------------------------------------ | -------------------------------- | -------------------------- | | useTokenSubscription | { chain, address } | TokenSubscribed[] | | useTokenCandlesSubscription | { chain, address, resolution } | TokenCandle[] | | useTokenTradesSubscription | { chain, address } | Trade[] | | useTokenActivitiesSubscription | { chain, address } | Activity[] | | useWalletPnlSubscription | { chain, address } | WalletPnlSubscribed[] | | useWalletPortfoliosSubscription | { chain, address } | PortfolioSubscribed[] | | useWalletPortfolioPnlsSubscription | { chain, address } | PortfolioPnlSubscribed[] | | useWalletTradesSubscription | { chain, address } | Trade[] | | useWalletActivitiesSubscription | { chain, address } | Activity[] | | useNewTokensSubscription | { chain } | TokenSubscribed[] | | useTrendingTokensSubscription | { chain } | TokenSubscribed[] | | useMigratedTokensSubscription | { chain } | TokenSubscribed[] | | useFinalStretchTokensSubscription | { chain } | TokenSubscribed[] | | useStockTokensSubscription | { chain } | TokenSubscribed[] |

Types

| Name | Definition | Description | | ------------------------- | ----------------------------------------------------------------- | ----------------------------------------- | | DexClientProviderProps | PropsWithChildren<{ client, subscribeClient, queryKeyPrefix? }> | Props for DexClientProvider. | | DexClientContextValue | { client, subscribeClient, queryKeyPrefix } | Shape of the context value. | | SubscriptionOptions | { enabled?: boolean; onError?: (error: Error) => void } | Options for subscription hooks. | | QueryHookConfig | { name, queryKey, fetch, defaultOptions? } | Config for createQueryHook. | | QueryHookReturn | { queryKey, fetch, useQuery } | Return type of createQueryHook. | | InfiniteQueryHookConfig | { name, queryKey, fetch } | Config for createInfiniteQueryHook. | | InfiniteQueryHookReturn | { queryKey, useInfiniteQuery } | Return type of createInfiniteQueryHook. |

Constants

| Name | Value | Description | | -------------------------- | ----------- | ---------------------------------- | | DEFAULT_QUERY_KEY_PREFIX | "liberfi" | Default prefix for all query keys. |

Factory Functions

createQueryHook<TParams, TData>(config): QueryHookReturn<TParams, TData>

Creates a useQuery-based hook, its queryKey builder, and a standalone fetch function from a single declarative config.

| Config Field | Type | Description | | ---------------- | ---------------------------------------------------------- | ---------------------------------------------- | | name | string | Key name segment (e.g. "token"). | | queryKey | (params: TParams) => string[] | Builds the variable segments of the query key. | | fetch | (client: API.IClient, params: TParams) => Promise<TData> | Fetches data using the API client. | | defaultOptions | Partial<Omit<UseQueryOptions, "queryKey" \| "queryFn">> | Default options merged below caller options. |

Returns { queryKey, fetch, useQuery }.

createInfiniteQueryHook<TParams, TData>(config): InfiniteQueryHookReturn<TParams, TData>

Creates a useInfiniteQuery-based hook and its queryKey builder for cursor-paginated endpoints. TData must extend { hasNext?: boolean; endCursor?: string }.

| Config Field | Type | Description | | ------------ | --------------------------------------------------------------------------------------- | ---------------------------------------------- | | name | string | Key name segment (e.g. "walletPortfolios"). | | queryKey | (params: TParams) => string[] | Builds the variable segments of the query key. | | fetch | (client: API.IClient, params: TParams, cursor: string \| undefined) => Promise<TData> | Fetches a single page of data. |

Returns { queryKey, useInfiniteQuery }.

Utility Functions

| Function | Signature | Description | | -------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------- | | toKeySegment | (value: string \| number \| boolean \| Date \| undefined \| null) => string | Serializes a primitive into a stable query key segment. | | toSortedKeySegment | (value: unknown[] \| undefined \| null) => string | Serializes an array into a sorted JSON string for query keys. |

Usage Examples

Basic setup

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Client } from "@liberfi.io/client";
import { DexClientProvider } from "@liberfi.io/react";

const queryClient = new QueryClient();
const client = new Client("your-api-token");

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <DexClientProvider client={client} subscribeClient={client}>
        <MyComponent />
      </DexClientProvider>
    </QueryClientProvider>
  );
}

Fetching token data

import { useTokenQuery } from "@liberfi.io/react";
import { Chain } from "@liberfi.io/types";

function TokenPrice({ address }: { address: string }) {
  const {
    data: token,
    isLoading,
    error,
  } = useTokenQuery({
    chain: Chain.SOLANA,
    address,
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {token.name}: ${token.marketData?.priceInUsd}
    </div>
  );
}

Real-time subscription

import { useCallback } from "react";
import { useTokenSubscription } from "@liberfi.io/react";
import { Chain, API } from "@liberfi.io/types";

function LiveTokenPrice({ address }: { address: string }) {
  const handleUpdate = useCallback((updates: API.TokenSubscribed[]) => {
    for (const update of updates) {
      console.log("New price:", update.marketData?.priceInUsd);
    }
  }, []);

  useTokenSubscription({ chain: Chain.SOLANA, address }, handleUpdate, {
    enabled: true,
  });

  return <div>Listening for updates...</div>;
}

Manual cache invalidation

import { useQueryClient } from "@tanstack/react-query";
import { tokenQueryKey } from "@liberfi.io/react";
import { Chain } from "@liberfi.io/types";

function RefreshButton({ address }: { address: string }) {
  const queryClient = useQueryClient();

  return (
    <button
      onClick={() =>
        queryClient.invalidateQueries({
          queryKey: tokenQueryKey({ chain: Chain.SOLANA, address }),
        })
      }
    >
      Refresh
    </button>
  );
}

Creating a custom query hook

import { createQueryHook } from "@liberfi.io/react";

interface MyDataParams {
  chain: string;
  id: string;
}

interface MyData {
  value: number;
}

const {
  queryKey: myDataQueryKey,
  fetch: fetchMyData,
  useQuery: useMyDataQuery,
} = createQueryHook<MyDataParams, MyData>({
  name: "myData",
  queryKey: (p) => [p.chain, p.id],
  fetch: (client, p) => client.getMyData(p.chain, p.id),
});

export { myDataQueryKey, fetchMyData, useMyDataQuery };

Infinite scrolling

import { useWalletPortfoliosInfiniteQuery } from "@liberfi.io/react";
import { Chain } from "@liberfi.io/types";

function PortfolioList({ walletAddress }: { walletAddress: string }) {
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
    useWalletPortfoliosInfiniteQuery({
      chain: Chain.SOLANA,
      address: walletAddress,
      limit: 20,
    });

  const portfolios = data?.pages.flatMap((page) => page.portfolios) ?? [];

  return (
    <div>
      {portfolios.map((p) => (
        <div key={p.address}>
          {p.symbol}: ${p.amountInUsd}
        </div>
      ))}
      {hasNextPage && (
        <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
          Load More
        </button>
      )}
    </div>
  );
}

Testing

Unit Tests

Unit tests mock the API client and verify hook behavior (query keys, fetch delegation, subscription lifecycle).

pnpm test

Integration Tests

Integration tests use the real @liberfi.io/client to verify end-to-end API communication.

Setup:

cp .env.test.example .env.test   # review and adjust URLs if needed

Required environment variables (in .env.test):

| Variable | Description | | ------------------------ | ------------------------------------------------- | | CHAINSTREAM_BASE_URL | REST API server URL | | API_BASE_URL | Auth API base URL (for fetching the access token) | | CHAINSTREAM_STREAM_URL | (optional) WebSocket URL, defaults to SDK value |

Run:

pnpm test:integration

When the environment variables are not set, integration tests are automatically skipped.

Architecture:

  • src/__integration__/fetch.integration.test.ts — Tests standalone fetch* functions against the real API in a Node environment. Validates API response shapes and end-to-end data flow.
  • src/__integration__/hooks.integration.test.tsx — Tests hooks via renderHook with a real client, verifying provider injection and full end-to-end data fetching through React Query. Uses a custom jsdom environment (jsdom-with-fetch.js) that injects Node's native fetch and removes XMLHttpRequest to bypass CORS.

Future Improvements

  • Add a standalone fetch function to infinite query hooks for API consistency.
  • Add a mutation key to useSendTxMutation for cache introspection.
  • Consolidate version.ts global side effect into a shared utility.
  • Document subscription deps-stability best practices for consumers.
  • Type useSubscriptionEffect deps more strictly (currently unknown[]).