@liberfi.io/react 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,279 @@
1
+ # @liberfi.io/react
2
+
3
+ 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.
4
+
5
+ ## Design Philosophy
6
+
7
+ - **Provider-based DI** — `DexClientProvider` injects `IClient` and `ISubscribeClient` instances via React context, keeping hooks decoupled from concrete implementations.
8
+ - **Factory-generated hooks** — `createQueryHook` and `createInfiniteQueryHook` generate hooks from a declarative config, ensuring consistent API surface (each hook exports `use*Query`, `*QueryKey`, and `fetch*`).
9
+ - **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.
10
+ - **Query key namespacing** — All keys are prefixed with a configurable namespace (default `"liberfi"`) to avoid collisions.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pnpm add @liberfi.io/react
16
+ ```
17
+
18
+ ### Peer Dependencies
19
+
20
+ The consumer must provide:
21
+
22
+ | Package | Version |
23
+ | ----------------------- | ------- |
24
+ | `react` | >= 18 |
25
+ | `react-dom` | >= 18 |
26
+ | `@tanstack/react-query` | ^5.90.2 |
27
+
28
+ ## API Reference
29
+
30
+ ### Components
31
+
32
+ #### `DexClientProvider`
33
+
34
+ Provides the Dex client instances and configuration to all descendant hooks.
35
+
36
+ | Prop | Type | Default | Description |
37
+ | ----------------- | ---------------------- | ----------- | --------------------------------------- |
38
+ | `client` | `API.IClient` | (required) | REST/HTTP client instance. |
39
+ | `subscribeClient` | `API.ISubscribeClient` | (required) | WebSocket subscription client instance. |
40
+ | `queryKeyPrefix` | `string` | `"liberfi"` | Prefix for all TanStack Query keys. |
41
+ | `children` | `ReactNode` | (required) | Child components. |
42
+
43
+ ### Hooks — Core
44
+
45
+ #### `useDexClient`
46
+
47
+ Returns the `DexClientContextValue` from the nearest `DexClientProvider`.
48
+
49
+ ```typescript
50
+ const { client, subscribeClient, queryKeyPrefix } = useDexClient();
51
+ ```
52
+
53
+ Throws if called outside a `DexClientProvider`.
54
+
55
+ ### Hooks — Query (27 hooks)
56
+
57
+ Each query hook is generated by `createQueryHook` and exports three items:
58
+
59
+ - `use*Query(params, options?)` — React hook returning `UseQueryResult<TData, Error>`.
60
+ - `*QueryKey(params, prefix?)` — Builds the full query key array for cache operations.
61
+ - `fetch*(client, params)` — Standalone fetch function for use outside React.
62
+
63
+ | Hook | Params | Return Data |
64
+ | ------------------------------------- | ----------------------------------------------------------- | ------------------------- |
65
+ | `useTokenQuery` | `{ chain, address }` | `Token` |
66
+ | `useTokensQuery` | `{ chain, addresses }` | `Token[]` |
67
+ | `useSearchTokensQuery` | `SearchTokensOptions` | `SearchTokenCursorList` |
68
+ | `useNewTokensQuery` | `{ chain, ...GetTokenListOptions }` | `Token[]` |
69
+ | `useTrendingTokensQuery` | `{ chain, resolution, ...GetTokenListOptions }` | `Token[]` |
70
+ | `useMigratedTokensQuery` | `{ chain, ...GetTokenListOptions }` | `Token[]` |
71
+ | `useFinalStretchTokensQuery` | `{ chain, ...GetTokenListOptions }` | `Token[]` |
72
+ | `useStockTokensQuery` | `{ chain, ...GetTokenListOptions }` | `Token[]` |
73
+ | `useSwapRouteQuery` | `SwapParams` | `SwapRoute` |
74
+ | `useTokenCandlesQuery` | `{ chain, address, resolution, ...GetTokenCandlesOptions }` | `TokenCandle[]` |
75
+ | `useTokenMarketDataQuery` | `{ chain, address }` | `TokenMarketData` |
76
+ | `useTokenSecurityQuery` | `{ chain, address }` | `TokenSecurity` |
77
+ | `useTokenStatsQuery` | `{ chain, address }` | `TokenStats` |
78
+ | `useTokenHoldersQuery` | `{ chain, address, ...CursorListOptions }` | `CursorList<TokenHolder>` |
79
+ | `useTokenTradesQuery` | `{ chain, address, ...GetTradesOptions }` | `CursorList<Trade>` |
80
+ | `useTokenActivitiesQuery` | `{ chain, address, ...GetActivitiesOptions }` | `CursorList<Activity>` |
81
+ | `useWalletPnlQuery` | `{ chain, address, resolution? }` | `WalletPnl` |
82
+ | `useWalletPortfoliosQuery` | `{ chain, address, ...paginationOptions }` | `WalletPortfolios` |
83
+ | `useWalletPortfoliosByTokensQuery` | `{ chain, address, tokenAddresses }` | `Portfolio[]` |
84
+ | `useWalletPortfolioPnlsQuery` | `{ chain, address, ...paginationOptions }` | `WalletPortfolioPnls` |
85
+ | `useWalletPortfolioPnlsByTokensQuery` | `{ chain, address, tokenAddresses }` | `PortfolioPnl[]` |
86
+ | `useWalletTradesQuery` | `{ chain, address, ...GetTradesOptions }` | `CursorList<Trade>` |
87
+ | `useWalletActivitiesQuery` | `{ chain, address, ...GetActivitiesOptions }` | `CursorList<Activity>` |
88
+ | `useTxSuccessQuery` | `{ chain, txHash, timeout? }` | `boolean` |
89
+ | `usePresignedUploadUrlQuery` | (none) | `string` |
90
+
91
+ #### Special hooks
92
+
93
+ | Hook | Type | Description |
94
+ | ------------------------------------- | -------------- | ------------------------------------------------------------------------------------------- |
95
+ | `useSendTxMutation` | Mutation | Sends a signed transaction. Returns `UseMutationResult<SendTxResult, Error, SendTxParams>`. |
96
+ | `useWalletPortfoliosInfiniteQuery` | Infinite Query | Cursor-paginated wallet holdings with `fetchNextPage()`. |
97
+ | `useWalletPortfolioPnlsInfiniteQuery` | Infinite Query | Cursor-paginated wallet PnLs with `fetchNextPage()`. |
98
+
99
+ ### Hooks — Subscription (14 hooks)
100
+
101
+ Each subscription hook manages WebSocket lifecycle automatically (subscribe on mount, unsubscribe on unmount or deps change).
102
+
103
+ **Signature:** `use*Subscription(params, callback, options?)`
104
+
105
+ | Hook | Params | Callback Data |
106
+ | ------------------------------------ | -------------------------------- | -------------------------- |
107
+ | `useTokenSubscription` | `{ chain, address }` | `TokenSubscribed[]` |
108
+ | `useTokenCandlesSubscription` | `{ chain, address, resolution }` | `TokenCandle[]` |
109
+ | `useTokenTradesSubscription` | `{ chain, address }` | `Trade[]` |
110
+ | `useTokenActivitiesSubscription` | `{ chain, address }` | `Activity[]` |
111
+ | `useWalletPnlSubscription` | `{ chain, address }` | `WalletPnlSubscribed[]` |
112
+ | `useWalletPortfoliosSubscription` | `{ chain, address }` | `PortfolioSubscribed[]` |
113
+ | `useWalletPortfolioPnlsSubscription` | `{ chain, address }` | `PortfolioPnlSubscribed[]` |
114
+ | `useWalletTradesSubscription` | `{ chain, address }` | `Trade[]` |
115
+ | `useWalletActivitiesSubscription` | `{ chain, address }` | `Activity[]` |
116
+ | `useNewTokensSubscription` | `{ chain }` | `TokenSubscribed[]` |
117
+ | `useTrendingTokensSubscription` | `{ chain }` | `TokenSubscribed[]` |
118
+ | `useMigratedTokensSubscription` | `{ chain }` | `TokenSubscribed[]` |
119
+ | `useFinalStretchTokensSubscription` | `{ chain }` | `TokenSubscribed[]` |
120
+ | `useStockTokensSubscription` | `{ chain }` | `TokenSubscribed[]` |
121
+
122
+ ### Types
123
+
124
+ | Name | Definition | Description |
125
+ | ------------------------ | ----------------------------------------------------------------- | ------------------------------- |
126
+ | `DexClientProviderProps` | `PropsWithChildren<{ client, subscribeClient, queryKeyPrefix? }>` | Props for `DexClientProvider`. |
127
+ | `DexClientContextValue` | `{ client, subscribeClient, queryKeyPrefix }` | Shape of the context value. |
128
+ | `SubscriptionOptions` | `{ enabled?: boolean; onError?: (error: Error) => void }` | Options for subscription hooks. |
129
+
130
+ ### Constants
131
+
132
+ | Name | Value | Description |
133
+ | -------------------------- | ----------- | ---------------------------------- |
134
+ | `DEFAULT_QUERY_KEY_PREFIX` | `"liberfi"` | Default prefix for all query keys. |
135
+
136
+ ### Utility Functions
137
+
138
+ | Function | Signature | Description |
139
+ | -------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------- |
140
+ | `toKeySegment` | `(value: string \| number \| boolean \| Date \| undefined \| null) => string` | Serializes a primitive into a stable query key segment. |
141
+ | `toSortedKeySegment` | `(value: unknown[] \| undefined \| null) => string` | Serializes an array into a sorted JSON string for query keys. |
142
+
143
+ ## Usage Examples
144
+
145
+ ### Basic setup
146
+
147
+ ```tsx
148
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
149
+ import { Client } from "@liberfi.io/client";
150
+ import { DexClientProvider } from "@liberfi.io/react";
151
+
152
+ const queryClient = new QueryClient();
153
+ const client = new Client("your-api-token");
154
+
155
+ function App() {
156
+ return (
157
+ <QueryClientProvider client={queryClient}>
158
+ <DexClientProvider client={client} subscribeClient={client}>
159
+ <MyComponent />
160
+ </DexClientProvider>
161
+ </QueryClientProvider>
162
+ );
163
+ }
164
+ ```
165
+
166
+ ### Fetching token data
167
+
168
+ ```tsx
169
+ import { useTokenQuery } from "@liberfi.io/react";
170
+ import { Chain } from "@liberfi.io/types";
171
+
172
+ function TokenPrice({ address }: { address: string }) {
173
+ const {
174
+ data: token,
175
+ isLoading,
176
+ error,
177
+ } = useTokenQuery({
178
+ chain: Chain.SOLANA,
179
+ address,
180
+ });
181
+
182
+ if (isLoading) return <div>Loading...</div>;
183
+ if (error) return <div>Error: {error.message}</div>;
184
+
185
+ return (
186
+ <div>
187
+ {token.name}: ${token.marketData?.priceInUsd}
188
+ </div>
189
+ );
190
+ }
191
+ ```
192
+
193
+ ### Real-time subscription
194
+
195
+ ```tsx
196
+ import { useCallback } from "react";
197
+ import { useTokenSubscription } from "@liberfi.io/react";
198
+ import { Chain, API } from "@liberfi.io/types";
199
+
200
+ function LiveTokenPrice({ address }: { address: string }) {
201
+ const handleUpdate = useCallback((updates: API.TokenSubscribed[]) => {
202
+ for (const update of updates) {
203
+ console.log("New price:", update.marketData?.priceInUsd);
204
+ }
205
+ }, []);
206
+
207
+ useTokenSubscription({ chain: Chain.SOLANA, address }, handleUpdate, {
208
+ enabled: true,
209
+ });
210
+
211
+ return <div>Listening for updates...</div>;
212
+ }
213
+ ```
214
+
215
+ ### Manual cache invalidation
216
+
217
+ ```tsx
218
+ import { useQueryClient } from "@tanstack/react-query";
219
+ import { tokenQueryKey } from "@liberfi.io/react";
220
+ import { Chain } from "@liberfi.io/types";
221
+
222
+ function RefreshButton({ address }: { address: string }) {
223
+ const queryClient = useQueryClient();
224
+
225
+ return (
226
+ <button
227
+ onClick={() =>
228
+ queryClient.invalidateQueries({
229
+ queryKey: tokenQueryKey({ chain: Chain.SOLANA, address }),
230
+ })
231
+ }
232
+ >
233
+ Refresh
234
+ </button>
235
+ );
236
+ }
237
+ ```
238
+
239
+ ### Infinite scrolling
240
+
241
+ ```tsx
242
+ import { useWalletPortfoliosInfiniteQuery } from "@liberfi.io/react";
243
+ import { Chain } from "@liberfi.io/types";
244
+
245
+ function PortfolioList({ walletAddress }: { walletAddress: string }) {
246
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
247
+ useWalletPortfoliosInfiniteQuery({
248
+ chain: Chain.SOLANA,
249
+ address: walletAddress,
250
+ limit: 20,
251
+ });
252
+
253
+ const portfolios = data?.pages.flatMap((page) => page.portfolios) ?? [];
254
+
255
+ return (
256
+ <div>
257
+ {portfolios.map((p) => (
258
+ <div key={p.address}>
259
+ {p.symbol}: ${p.amountInUsd}
260
+ </div>
261
+ ))}
262
+ {hasNextPage && (
263
+ <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
264
+ Load More
265
+ </button>
266
+ )}
267
+ </div>
268
+ );
269
+ }
270
+ ```
271
+
272
+ ## Future Improvements
273
+
274
+ - Export `createQueryHook` and `createInfiniteQueryHook` factories so consumers can create custom hooks.
275
+ - Add a standalone `fetch` function to infinite query hooks for API consistency.
276
+ - Add a mutation key to `useSendTxMutation` for cache introspection.
277
+ - Consolidate `version.ts` global side effect into a shared utility.
278
+ - Document subscription deps-stability best practices for consumers.
279
+ - Type `useSubscriptionEffect` deps more strictly (currently `unknown[]`).