@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 +279 -0
- package/dist/index.d.mts +251 -119
- package/dist/index.d.ts +251 -119
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -3
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[]`).
|