@hfunlabs/hypurr-connect 0.1.0

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,363 @@
1
+ # @hfunlabs/hypurr-connect
2
+
3
+ React authentication and wallet connectivity library for the [Hyperliquid](https://hyperliquid.xyz) decentralized exchange via the [Hypurr](https://hypurr.fun) gRPC backend. Provides two authentication paths — **Telegram OAuth** and **EOA wallet** (MetaMask / browser wallet) — with a unified `ExchangeClient` API for placing orders, managing positions, and executing any L1 action.
4
+
5
+ ## Features
6
+
7
+ - **Dual auth flows** — Telegram OAuth (server-side signing) and EOA wallet (client-side agent key signing)
8
+ - **Unified exchange client** — Same `ExchangeClient` interface regardless of auth method
9
+ - **gRPC transport** — Custom transport that routes exchange actions through the Hypurr backend for Telegram users
10
+ - **Agent key management** — Automatic generation, on-chain approval, and localStorage persistence of agent keys for EOA wallets
11
+ - **Session persistence** — Auth state survives page reloads via localStorage
12
+ - **Responsive login modal** — Centered modal on desktop, bottom drawer on mobile with framer-motion animations
13
+ - **USDC balance tracking** — Built-in clearinghouse balance fetching with manual refresh
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add @hfunlabs/hypurr-connect
19
+ ```
20
+
21
+ ### Peer Dependencies
22
+
23
+ Install the required peer dependencies:
24
+
25
+ ```bash
26
+ pnpm add @hfunlabs/hyperliquid @protobuf-ts/grpcweb-transport @protobuf-ts/runtime-rpc framer-motion react
27
+ ```
28
+
29
+ | Peer Dependency | Version |
30
+ | -------------------------------- | ------------------- |
31
+ | `@hfunlabs/hyperliquid` | `0.30.2-hfunlabs.2` |
32
+ | `@protobuf-ts/grpcweb-transport` | `>=2.0.0` |
33
+ | `@protobuf-ts/runtime-rpc` | `>=2.0.0` |
34
+ | `framer-motion` | `>=10.0.0` |
35
+ | `react` | `>=18.0.0` |
36
+
37
+ ## Quick Start
38
+
39
+ ### 1. Wrap your app with the provider
40
+
41
+ ```tsx
42
+ import { HypurrConnectProvider } from "@hfunlabs/hypurr-connect";
43
+
44
+ const config = {
45
+ isTestnet: false,
46
+ telegram: {
47
+ botUsername: "YourBot",
48
+ botId: "123456789",
49
+ },
50
+ };
51
+
52
+ function App() {
53
+ return (
54
+ <HypurrConnectProvider config={config}>
55
+ <YourApp />
56
+ </HypurrConnectProvider>
57
+ );
58
+ }
59
+ ```
60
+
61
+ ### 2. Use the hook anywhere in your app
62
+
63
+ ```tsx
64
+ import { useHypurrConnect } from "@hfunlabs/hypurr-connect";
65
+
66
+ function TradingPanel() {
67
+ const { user, isLoggedIn, exchange, usdcBalance, openLoginModal, logout } =
68
+ useHypurrConnect();
69
+
70
+ if (!isLoggedIn) {
71
+ return <button onClick={openLoginModal}>Connect</button>;
72
+ }
73
+
74
+ return (
75
+ <div>
76
+ <p>Welcome, {user.displayName}</p>
77
+ <p>Balance: {usdcBalance} USDC</p>
78
+ <button onClick={logout}>Disconnect</button>
79
+ </div>
80
+ );
81
+ }
82
+ ```
83
+
84
+ ### 3. Add the login modal
85
+
86
+ ```tsx
87
+ import { LoginModal, useHypurrConnect } from "@hfunlabs/hypurr-connect";
88
+
89
+ function AppShell() {
90
+ const { loginModalOpen, closeLoginModal } = useHypurrConnect();
91
+
92
+ return (
93
+ <>
94
+ {loginModalOpen && (
95
+ <LoginModal
96
+ onConnectWallet={() => {
97
+ // Open your wagmi/RainbowKit wallet modal here
98
+ }}
99
+ />
100
+ )}
101
+ <MainContent />
102
+ </>
103
+ );
104
+ }
105
+ ```
106
+
107
+ ## Configuration
108
+
109
+ ### `HypurrConnectConfig`
110
+
111
+ ```typescript
112
+ interface HypurrConnectConfig {
113
+ grpcTimeout?: number; // Request timeout in ms (default: 15000)
114
+ isTestnet?: boolean; // Use testnet endpoints (default: false)
115
+ telegram?: {
116
+ botUsername: string; // Telegram bot username for the login widget
117
+ botId: string; // Telegram bot ID for the OAuth URL
118
+ };
119
+ }
120
+ ```
121
+
122
+ The `telegram` block is only required if you want to support Telegram login.
123
+
124
+ ### Dependencies
125
+
126
+ This package depends on [`hypurr-grpc`](https://gitlab.com/hypurr/hypurr-grpc) (public GitLab repo) for generated protobuf service clients. It is installed directly from Git — no registry auth is needed.
127
+
128
+ ## Authentication Flows
129
+
130
+ ### Telegram Login
131
+
132
+ 1. User clicks "Telegram" in the `LoginModal`
133
+ 2. A popup opens to `oauth.telegram.org` with the configured bot
134
+ 3. User authorizes; Telegram posts auth data back via `postMessage`
135
+ 4. The provider calls the Hypurr gRPC backend (`telegramUser`) to fetch the user's wallet address and ID
136
+ 5. An `ExchangeClient` is created with `GrpcExchangeTransport` — all exchange actions are signed **server-side** by the Hypurr backend
137
+ 6. Session is persisted in localStorage (`hypurr-connect-tg-user`)
138
+
139
+ ### EOA Wallet Login
140
+
141
+ 1. User clicks "Wallet" in the `LoginModal`; the `onConnectWallet` callback fires
142
+ 2. Your app connects the wallet (e.g., via wagmi) and calls `loginEoa(address)`
143
+ 3. The user must approve an agent key before trading:
144
+ - Call `approveAgent(signTypedDataAsync)` with the wallet's signing function
145
+ - A random agent key pair is generated locally
146
+ - The wallet signs an EIP-712 `ApproveAgent` message
147
+ - The approval is submitted to Hyperliquid
148
+ - The agent key is persisted in localStorage (`hypurr-connect-agent:{address}`)
149
+ 4. An `ExchangeClient` is created with `HttpTransport` + `PrivateKeySigner` — all exchange actions are signed **client-side** with the agent key
150
+
151
+ ### Using the Exchange Client
152
+
153
+ Once authenticated (and agent approved for EOA), the `exchange` object from `useHypurrConnect()` is a fully functional `ExchangeClient` from `@hfunlabs/hyperliquid`. Use it for any L1 action:
154
+
155
+ ```tsx
156
+ const { exchange, agentReady } = useHypurrConnect();
157
+
158
+ // Place an order (works identically for Telegram and EOA users)
159
+ if (exchange) {
160
+ const result = await exchange.placeOrder({
161
+ asset: 0,
162
+ isBuy: true,
163
+ limitPx: "50000",
164
+ sz: "0.001",
165
+ orderType: { limit: { tif: "Gtc" } },
166
+ });
167
+ }
168
+ ```
169
+
170
+ ## API Reference
171
+
172
+ ### Components
173
+
174
+ #### `HypurrConnectProvider`
175
+
176
+ ```tsx
177
+ <HypurrConnectProvider config={HypurrConnectConfig}>
178
+ {children}
179
+ </HypurrConnectProvider>
180
+ ```
181
+
182
+ Context provider that manages all auth state, gRPC clients, exchange clients, and balance tracking. Must wrap any component that uses `useHypurrConnect`.
183
+
184
+ #### `LoginModal`
185
+
186
+ ```tsx
187
+ <LoginModal
188
+ onConnectWallet={() => void}
189
+ walletIcon?: ReactNode // Custom icon for the wallet button (defaults to MetaMask icon)
190
+ />
191
+ ```
192
+
193
+ Animated modal with Telegram and Wallet login buttons. Renders as a centered modal on desktop (>=640px) and a bottom drawer on mobile. Uses the `closeLoginModal` function from context to dismiss.
194
+
195
+ ### Hook
196
+
197
+ #### `useHypurrConnect(): HypurrConnectState`
198
+
199
+ Returns the full auth and exchange state. Throws if used outside `HypurrConnectProvider`.
200
+
201
+ ### `HypurrConnectState`
202
+
203
+ | Property | Type | Description |
204
+ | -------------------- | -------------------------------------------------------- | ---------------------------------------------- |
205
+ | `user` | `HypurrUser \| null` | Current authenticated user |
206
+ | `isLoggedIn` | `boolean` | Whether a user is authenticated |
207
+ | `isLoading` | `boolean` | Whether auth is in progress |
208
+ | `error` | `string \| null` | Last auth error message |
209
+ | `authMethod` | `AuthMethod` | `"telegram"`, `"eoa"`, or `null` |
210
+ | `exchange` | `ExchangeClient \| null` | Hyperliquid exchange client for L1 actions |
211
+ | `usdcBalance` | `string \| null` | User's USDC balance from the clearinghouse |
212
+ | `usdcBalanceLoading` | `boolean` | Whether balance is being fetched |
213
+ | `refreshBalance` | `() => void` | Manually re-fetch USDC balance |
214
+ | `loginModalOpen` | `boolean` | Whether the login modal is visible |
215
+ | `openLoginModal` | `() => void` | Show the login modal |
216
+ | `closeLoginModal` | `() => void` | Hide the login modal |
217
+ | `loginTelegram` | `(data: TelegramLoginData) => void` | Authenticate with Telegram OAuth data |
218
+ | `loginEoa` | `(address: \`0x\${string}\`) => void` | Authenticate with an EOA wallet address |
219
+ | `logout` | `() => void` | Clear all auth state and localStorage |
220
+ | `agent` | `StoredAgent \| null` | Current agent key (EOA flow only) |
221
+ | `agentReady` | `boolean` | Whether the agent key is approved and ready |
222
+ | `approveAgent` | `(signTypedDataAsync: SignTypedDataFn) => Promise<void>` | Generate and approve a new agent key |
223
+ | `clearAgent` | `() => void` | Remove the agent key from state and storage |
224
+ | `botId` | `string` | Telegram bot ID from config |
225
+ | `authDataMap` | `Record<string, string>` | Raw Telegram auth data as key-value pairs |
226
+ | `telegramClient` | `TelegramClient` | Low-level gRPC client for the Telegram service |
227
+ | `staticClient` | `StaticClient` | Low-level gRPC client for the Static service |
228
+
229
+ ### Types
230
+
231
+ #### `HypurrUser`
232
+
233
+ ```typescript
234
+ interface HypurrUser {
235
+ address: string; // Ethereum address
236
+ walletId: number; // Hypurr wallet ID
237
+ displayName: string; // "@username", first_name, or truncated address
238
+ photoUrl?: string; // Telegram profile photo URL
239
+ authMethod: AuthMethod; // "telegram" | "eoa"
240
+ telegramId?: string; // Telegram user ID (string)
241
+ }
242
+ ```
243
+
244
+ #### `AuthMethod`
245
+
246
+ ```typescript
247
+ type AuthMethod = "telegram" | "eoa" | null;
248
+ ```
249
+
250
+ #### `TelegramLoginData`
251
+
252
+ ```typescript
253
+ interface TelegramLoginData {
254
+ id: number;
255
+ first_name: string;
256
+ last_name?: string;
257
+ username?: string;
258
+ photo_url?: string;
259
+ auth_date: number;
260
+ hash: string;
261
+ }
262
+ ```
263
+
264
+ #### `StoredAgent`
265
+
266
+ ```typescript
267
+ interface StoredAgent {
268
+ privateKey: `0x${string}`;
269
+ address: `0x${string}`;
270
+ approvedAt: number; // Timestamp when approved on-chain
271
+ }
272
+ ```
273
+
274
+ #### `SignTypedDataFn`
275
+
276
+ ```typescript
277
+ type SignTypedDataFn = (params: {
278
+ domain: Record<string, unknown>;
279
+ types: Record<string, { name: string; type: string }[]>;
280
+ primaryType: string;
281
+ message: Record<string, unknown>;
282
+ }) => Promise<`0x${string}`>;
283
+ ```
284
+
285
+ ### Classes
286
+
287
+ #### `GrpcExchangeTransport`
288
+
289
+ Custom `IRequestTransport` implementation that routes exchange actions through the Hypurr gRPC backend (used by Telegram auth).
290
+
291
+ ```typescript
292
+ class GrpcExchangeTransport implements IRequestTransport {
293
+ isTestnet: boolean;
294
+ constructor(config: GrpcExchangeTransportConfig);
295
+ request<T>(
296
+ endpoint: "info" | "exchange" | "explorer",
297
+ payload: unknown,
298
+ signal?: AbortSignal,
299
+ ): Promise<T>;
300
+ }
301
+ ```
302
+
303
+ - **`exchange` endpoint** — Serializes the action to bytes and calls `telegramClient.hyperliquidCoreAction()` via gRPC. The Hypurr backend validates the auth data and signs the action server-side.
304
+ - **`info` / `explorer` endpoints** — Proxied directly to the Hyperliquid HTTP API.
305
+
306
+ ```typescript
307
+ interface GrpcExchangeTransportConfig {
308
+ isTestnet?: boolean;
309
+ telegramClient: TelegramClient;
310
+ authDataMap: Record<string, string>;
311
+ walletId: number;
312
+ }
313
+ ```
314
+
315
+ ### Factory Functions
316
+
317
+ #### `createTelegramClient(config: HypurrConnectConfig): TelegramClient`
318
+
319
+ Creates a gRPC-Web client for the Telegram service.
320
+
321
+ #### `createStaticClient(config: HypurrConnectConfig): StaticClient`
322
+
323
+ Creates a gRPC-Web client for the Static service.
324
+
325
+ Both use `GrpcWebFetchTransport` with the configured `baseUrl`, `timeout`, and `origin` metadata.
326
+
327
+ ## localStorage Keys
328
+
329
+ | Key | Content |
330
+ | -------------------------------- | -------------------------------------------------------------- |
331
+ | `hypurr-connect-tg-user` | Serialized `TelegramLoginData` (persists Telegram session) |
332
+ | `hypurr-connect-agent:{address}` | Serialized `StoredAgent` (persists EOA agent keys per address) |
333
+
334
+ ## Architecture
335
+
336
+ ```
337
+ ┌─────────────────────────────────────────────────────────┐
338
+ │ HypurrConnectProvider │
339
+ │ │
340
+ │ ┌───────────────┐ ┌────────────────────────┐ │
341
+ │ │ Telegram Auth │──gRPC──▶ │ GrpcExchangeTransport │ │
342
+ │ │ (server- │ │ ┌──────────────────┐ │ │
343
+ │ │ signed) │ │ │ telegramClient │ │ │
344
+ │ └───────────────┘ │ │ .hyperliquidCore │ │ │
345
+ │ │ │ Action() │ │ │
346
+ │ ┌───────────────┐ │ └──────────────────┘ │ │
347
+ │ │ EOA Auth │──HTTP──▶ │ HttpTransport │ │
348
+ │ │ (agent key │ │ + PrivateKeySigner │ │
349
+ │ │ signed) │ └────────────────────────┘ │
350
+ │ └───────────────┘ │ │
351
+ │ ▼ │
352
+ │ ┌──────────────┐ │
353
+ │ │ExchangeClient│ │
354
+ │ └──────────────┘ │
355
+ │ │ │
356
+ │ ▼ │
357
+ │ Hyperliquid L1 │
358
+ └─────────────────────────────────────────────────────────┘
359
+ ```
360
+
361
+ ## License
362
+
363
+ MIT
@@ -0,0 +1,113 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { ExchangeClient, IRequestTransport } from '@hfunlabs/hyperliquid';
4
+ import { StaticClient } from 'hypurr-grpc/ts/hypurr/static/static_service.client';
5
+ import { TelegramClient } from 'hypurr-grpc/ts/hypurr/telegram/telegram_service.client';
6
+
7
+ interface HypurrConnectConfig {
8
+ grpcTimeout?: number;
9
+ isTestnet?: boolean;
10
+ telegram?: {
11
+ botUsername: string;
12
+ botId: string;
13
+ };
14
+ }
15
+ interface TelegramLoginData {
16
+ id: number;
17
+ first_name: string;
18
+ last_name?: string;
19
+ username?: string;
20
+ photo_url?: string;
21
+ auth_date: number;
22
+ hash: string;
23
+ }
24
+ type AuthMethod = "telegram" | "eoa" | null;
25
+ interface HypurrUser {
26
+ address: string;
27
+ walletId: number;
28
+ displayName: string;
29
+ photoUrl?: string;
30
+ authMethod: AuthMethod;
31
+ telegramId?: string;
32
+ }
33
+ interface StoredAgent {
34
+ privateKey: `0x${string}`;
35
+ address: `0x${string}`;
36
+ approvedAt: number;
37
+ }
38
+ type SignTypedDataFn = (params: {
39
+ domain: Record<string, unknown>;
40
+ types: Record<string, {
41
+ name: string;
42
+ type: string;
43
+ }[]>;
44
+ primaryType: string;
45
+ message: Record<string, unknown>;
46
+ }) => Promise<`0x${string}`>;
47
+ interface HypurrConnectState {
48
+ user: HypurrUser | null;
49
+ isLoggedIn: boolean;
50
+ isLoading: boolean;
51
+ error: string | null;
52
+ authMethod: AuthMethod;
53
+ exchange: ExchangeClient<any> | null;
54
+ usdcBalance: string | null;
55
+ usdcBalanceLoading: boolean;
56
+ refreshBalance: () => void;
57
+ loginModalOpen: boolean;
58
+ openLoginModal: () => void;
59
+ closeLoginModal: () => void;
60
+ loginTelegram: (data: TelegramLoginData) => void;
61
+ loginEoa: (address: `0x${string}`) => void;
62
+ logout: () => void;
63
+ agent: StoredAgent | null;
64
+ agentReady: boolean;
65
+ approveAgent: (signTypedDataAsync: SignTypedDataFn) => Promise<void>;
66
+ clearAgent: () => void;
67
+ botId: string;
68
+ authDataMap: Record<string, string>;
69
+ telegramClient: TelegramClient;
70
+ staticClient: StaticClient;
71
+ }
72
+
73
+ declare function useHypurrConnect(): HypurrConnectState;
74
+ declare function HypurrConnectProvider({ config, children, }: {
75
+ config: HypurrConnectConfig;
76
+ children: ReactNode;
77
+ }): react_jsx_runtime.JSX.Element;
78
+
79
+ interface LoginModalProps {
80
+ onConnectWallet: () => void;
81
+ walletIcon?: ReactNode;
82
+ }
83
+ declare function LoginModal({ onConnectWallet, walletIcon }: LoginModalProps): react_jsx_runtime.JSX.Element;
84
+
85
+ interface GrpcExchangeTransportConfig {
86
+ isTestnet?: boolean;
87
+ telegramClient: TelegramClient;
88
+ authDataMap: Record<string, string>;
89
+ walletId: number;
90
+ }
91
+ /**
92
+ * Routes exchange requests through the Hypurr gRPC backend (HyperliquidCoreAction)
93
+ * for server-side signing. The backend handles signature generation.
94
+ *
95
+ * The SDK-generated nonce is forwarded; the action payload is JSON-encoded as bytes.
96
+ * Info/explorer requests are proxied directly to the Hyperliquid HTTP API.
97
+ */
98
+ declare class GrpcExchangeTransport implements IRequestTransport {
99
+ isTestnet: boolean;
100
+ private telegramClient;
101
+ private authDataMap;
102
+ private walletId;
103
+ private infoUrl;
104
+ constructor(config: GrpcExchangeTransportConfig);
105
+ request<T>(endpoint: "info" | "exchange" | "explorer", payload: unknown, signal?: AbortSignal): Promise<T>;
106
+ private exchangeViaGrpc;
107
+ private directRequest;
108
+ }
109
+
110
+ declare function createTelegramClient(config: HypurrConnectConfig): TelegramClient;
111
+ declare function createStaticClient(config: HypurrConnectConfig): StaticClient;
112
+
113
+ export { type AuthMethod, GrpcExchangeTransport, type GrpcExchangeTransportConfig, type HypurrConnectConfig, HypurrConnectProvider, type HypurrConnectState, type HypurrUser, LoginModal, type LoginModalProps, type SignTypedDataFn, type StoredAgent, type TelegramLoginData, createStaticClient, createTelegramClient, useHypurrConnect };