@hfunlabs/hypurr-connect 0.1.0 → 0.1.2
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 +162 -40
- package/dist/index.d.ts +32 -6
- package/dist/index.js +464 -168
- package/dist/index.js.map +1 -1
- package/package.json +13 -11
- package/src/HypurrConnectProvider.tsx +332 -158
- package/src/LoginModal.tsx +162 -28
- package/src/agent.ts +80 -0
- package/src/icons/MetaMaskColorIcon.tsx +5 -3
- package/src/icons/TelegramColorIcon.tsx +8 -6
- package/src/index.ts +4 -0
- package/src/types.ts +38 -7
package/README.md
CHANGED
|
@@ -6,11 +6,11 @@ React authentication and wallet connectivity library for the [Hyperliquid](https
|
|
|
6
6
|
|
|
7
7
|
- **Dual auth flows** — Telegram OAuth (server-side signing) and EOA wallet (client-side agent key signing)
|
|
8
8
|
- **Unified exchange client** — Same `ExchangeClient` interface regardless of auth method
|
|
9
|
+
- **Multi-wallet management** — Switch between wallets, create/delete wallets, manage wallet packs and labels (Telegram users)
|
|
9
10
|
- **gRPC transport** — Custom transport that routes exchange actions through the Hypurr backend for Telegram users
|
|
10
|
-
- **Agent key management** —
|
|
11
|
+
- **Agent key management** — Named agent keys (`"hypurr-connect"`) with on-chain approval, `extraAgents` validation, expiry-aware caching, and automatic dead-agent recovery
|
|
11
12
|
- **Session persistence** — Auth state survives page reloads via localStorage
|
|
12
13
|
- **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
14
|
|
|
15
15
|
## Installation
|
|
16
16
|
|
|
@@ -64,7 +64,7 @@ function App() {
|
|
|
64
64
|
import { useHypurrConnect } from "@hfunlabs/hypurr-connect";
|
|
65
65
|
|
|
66
66
|
function TradingPanel() {
|
|
67
|
-
const { user, isLoggedIn, exchange,
|
|
67
|
+
const { user, isLoggedIn, exchange, openLoginModal, logout } =
|
|
68
68
|
useHypurrConnect();
|
|
69
69
|
|
|
70
70
|
if (!isLoggedIn) {
|
|
@@ -74,7 +74,6 @@ function TradingPanel() {
|
|
|
74
74
|
return (
|
|
75
75
|
<div>
|
|
76
76
|
<p>Welcome, {user.displayName}</p>
|
|
77
|
-
<p>Balance: {usdcBalance} USDC</p>
|
|
78
77
|
<button onClick={logout}>Disconnect</button>
|
|
79
78
|
</div>
|
|
80
79
|
);
|
|
@@ -138,22 +137,42 @@ This package depends on [`hypurr-grpc`](https://gitlab.com/hypurr/hypurr-grpc) (
|
|
|
138
137
|
|
|
139
138
|
### EOA Wallet Login
|
|
140
139
|
|
|
140
|
+
The EOA flow is split into two steps — **connect** and **approve** — so that the synchronous wallet connection never blocks on the signing prompt:
|
|
141
|
+
|
|
141
142
|
1. User clicks "Wallet" in the `LoginModal`; the `onConnectWallet` callback fires
|
|
142
|
-
2. Your app connects the wallet (e.g., via wagmi) and calls `
|
|
143
|
-
3.
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
- The
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
143
|
+
2. Your app connects the wallet (e.g., via wagmi) and calls `connectEoa(address)` — this is **synchronous**, sets the user immediately, and restores a cached agent from localStorage if one is still valid
|
|
144
|
+
3. When you need exchange functionality, call `approveAgent(signTypedDataAsync, chainId)`:
|
|
145
|
+
- If a valid cached agent exists, it is validated against Hyperliquid's `extraAgents` endpoint and its `validUntil` timestamp — reused only if still active
|
|
146
|
+
- If no valid agent exists, a fresh key pair is generated and approved on-chain as a **named agent** (`"hypurr-connect"`) via the SDK's `approveAgent`. Named agents are only replaced when another `ApproveAgent` is sent with the same name, so they won't be pruned by unrelated unnamed agent registrations
|
|
147
|
+
- The agent key and its `validUntil` are persisted in localStorage (`hypurr-connect-agent:{address}`)
|
|
148
|
+
4. Check `agentReady` to know whether the exchange client can sign transactions. If `agentReady` is `false` and you call any method on `exchange`, the SDK throws an explicit error:
|
|
149
|
+
> `[HypurrConnect] No agent key approved. Call approveAgent(signTypedDataAsync) before using the exchange client.`
|
|
150
|
+
5. Once the agent is approved, the `ExchangeClient` uses `HttpTransport` + `PrivateKeySigner` — all exchange actions are signed **client-side** with the agent key
|
|
151
|
+
6. If an exchange action fails because the agent was pruned or expired, the SDK automatically clears the dead agent and surfaces the error via `error` so the consumer can prompt re-login
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
// Step 1: Connect wallet (synchronous — safe to call from useEffect)
|
|
155
|
+
const { connectEoa, approveAgent, agentReady, exchange } = useHypurrConnect();
|
|
156
|
+
|
|
157
|
+
// Call when wallet connects (e.g., wagmi onConnect callback)
|
|
158
|
+
connectEoa(address);
|
|
159
|
+
|
|
160
|
+
// Step 2: Approve agent (async — triggers wallet signing prompt)
|
|
161
|
+
// Pass the connected wallet's chain ID so the EIP-712 domain matches
|
|
162
|
+
await approveAgent(signTypedDataAsync, chainId);
|
|
163
|
+
|
|
164
|
+
// Now exchange is ready to use
|
|
165
|
+
if (agentReady && exchange) {
|
|
166
|
+
await exchange.placeOrder(/* ... */);
|
|
167
|
+
}
|
|
168
|
+
```
|
|
150
169
|
|
|
151
170
|
### Using the Exchange Client
|
|
152
171
|
|
|
153
|
-
Once authenticated
|
|
172
|
+
Once authenticated, the `exchange` object from `useHypurrConnect()` is a fully functional `ExchangeClient` from `@hfunlabs/hyperliquid`. Use it for any L1 action:
|
|
154
173
|
|
|
155
174
|
```tsx
|
|
156
|
-
const { exchange
|
|
175
|
+
const { exchange } = useHypurrConnect();
|
|
157
176
|
|
|
158
177
|
// Place an order (works identically for Telegram and EOA users)
|
|
159
178
|
if (exchange) {
|
|
@@ -167,6 +186,74 @@ if (exchange) {
|
|
|
167
186
|
}
|
|
168
187
|
```
|
|
169
188
|
|
|
189
|
+
### Multi-Wallet Management (Telegram)
|
|
190
|
+
|
|
191
|
+
Telegram users can have multiple wallets. The library exposes the full wallet list and lets you switch the active wallet — the `exchange` client and `user` automatically update.
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
const {
|
|
195
|
+
wallets,
|
|
196
|
+
selectedWalletId,
|
|
197
|
+
selectWallet,
|
|
198
|
+
createWallet,
|
|
199
|
+
deleteWallet,
|
|
200
|
+
refreshWallets,
|
|
201
|
+
} = useHypurrConnect();
|
|
202
|
+
|
|
203
|
+
// List wallets
|
|
204
|
+
wallets.map((w) => (
|
|
205
|
+
<button
|
|
206
|
+
key={w.id}
|
|
207
|
+
onClick={() => selectWallet(w.id)}
|
|
208
|
+
style={{ fontWeight: w.id === selectedWalletId ? "bold" : "normal" }}
|
|
209
|
+
>
|
|
210
|
+
{w.name || w.ethereumAddress}
|
|
211
|
+
</button>
|
|
212
|
+
));
|
|
213
|
+
|
|
214
|
+
// Create a new wallet
|
|
215
|
+
const newWallet = await createWallet("Trading");
|
|
216
|
+
|
|
217
|
+
// Delete a wallet (auto-selects another if the deleted one was active)
|
|
218
|
+
await deleteWallet(walletId);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### Wallet Packs & Labels
|
|
222
|
+
|
|
223
|
+
Organize watched wallets into named packs with labels:
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
const {
|
|
227
|
+
packs,
|
|
228
|
+
createWalletPack,
|
|
229
|
+
addPackLabel,
|
|
230
|
+
modifyPackLabel,
|
|
231
|
+
removePackLabel,
|
|
232
|
+
} = useHypurrConnect();
|
|
233
|
+
|
|
234
|
+
// Create a pack
|
|
235
|
+
const packId = await createWalletPack("Whales");
|
|
236
|
+
|
|
237
|
+
// Add a labeled wallet to the pack
|
|
238
|
+
await addPackLabel({
|
|
239
|
+
walletAddress: "0x...",
|
|
240
|
+
walletLabel: "Big trader",
|
|
241
|
+
packId,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Rename a label
|
|
245
|
+
await modifyPackLabel({
|
|
246
|
+
walletLabelOld: "Big trader",
|
|
247
|
+
walletLabelNew: "Whale #1",
|
|
248
|
+
packId,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Remove a label from the pack
|
|
252
|
+
await removePackLabel({ walletLabel: "Whale #1", packId });
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
All wallet management functions automatically refresh the wallet list from the server after mutations.
|
|
256
|
+
|
|
170
257
|
## API Reference
|
|
171
258
|
|
|
172
259
|
### Components
|
|
@@ -179,7 +266,7 @@ if (exchange) {
|
|
|
179
266
|
</HypurrConnectProvider>
|
|
180
267
|
```
|
|
181
268
|
|
|
182
|
-
Context provider that manages all auth state, gRPC clients, exchange clients
|
|
269
|
+
Context provider that manages all auth state, gRPC clients, and exchange clients. Must wrap any component that uses `useHypurrConnect`.
|
|
183
270
|
|
|
184
271
|
#### `LoginModal`
|
|
185
272
|
|
|
@@ -200,31 +287,38 @@ Returns the full auth and exchange state. Throws if used outside `HypurrConnectP
|
|
|
200
287
|
|
|
201
288
|
### `HypurrConnectState`
|
|
202
289
|
|
|
203
|
-
| Property
|
|
204
|
-
|
|
|
205
|
-
| `user`
|
|
206
|
-
| `isLoggedIn`
|
|
207
|
-
| `isLoading`
|
|
208
|
-
| `error`
|
|
209
|
-
| `authMethod`
|
|
210
|
-
| `exchange`
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
| `
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
222
|
-
| `
|
|
223
|
-
| `
|
|
224
|
-
| `
|
|
225
|
-
| `
|
|
226
|
-
| `
|
|
227
|
-
| `
|
|
290
|
+
| Property | Type | Description |
|
|
291
|
+
| ------------------ | ------------------------------------------------------------------------- | ---------------------------------------------------------------- |
|
|
292
|
+
| `user` | `HypurrUser \| null` | Current authenticated user (reflects selected wallet) |
|
|
293
|
+
| `isLoggedIn` | `boolean` | Whether a user is authenticated |
|
|
294
|
+
| `isLoading` | `boolean` | Whether auth is in progress |
|
|
295
|
+
| `error` | `string \| null` | Last auth or dead-agent error message |
|
|
296
|
+
| `authMethod` | `AuthMethod` | `"telegram"`, `"eoa"`, or `null` |
|
|
297
|
+
| `exchange` | `ExchangeClient \| null` | Hyperliquid exchange client for L1 actions |
|
|
298
|
+
| `wallets` | `HyperliquidWallet[]` | All wallets for the Telegram user (empty for EOA) |
|
|
299
|
+
| `selectedWalletId` | `number` | ID of the currently active wallet |
|
|
300
|
+
| `selectWallet` | `(walletId: number) => void` | Switch the active wallet |
|
|
301
|
+
| `createWallet` | `(name: string) => Promise<HyperliquidWallet>` | Create a new wallet (Telegram only) |
|
|
302
|
+
| `deleteWallet` | `(walletId: number) => Promise<void>` | Delete a wallet (Telegram only) |
|
|
303
|
+
| `refreshWallets` | `() => void` | Re-fetch wallets and packs from the server |
|
|
304
|
+
| `packs` | `TelegramChatWalletPack[]` | Wallet packs for the Telegram user |
|
|
305
|
+
| `createWalletPack` | `(name: string) => Promise<number>` | Create a wallet pack; returns the new pack ID |
|
|
306
|
+
| `addPackLabel` | `(params) => Promise<void>` | Add a labeled wallet to a pack |
|
|
307
|
+
| `modifyPackLabel` | `(params) => Promise<void>` | Rename a label within a pack |
|
|
308
|
+
| `removePackLabel` | `(params) => Promise<void>` | Remove a label from a pack |
|
|
309
|
+
| `loginModalOpen` | `boolean` | Whether the login modal is visible |
|
|
310
|
+
| `openLoginModal` | `() => void` | Show the login modal |
|
|
311
|
+
| `closeLoginModal` | `() => void` | Hide the login modal |
|
|
312
|
+
| `connectEoa` | `(address: \`0x\${string}\`) => void` | Connect EOA wallet (sync, no signing) |
|
|
313
|
+
| `approveAgent` | `(signTypedDataAsync: SignTypedDataFn, chainId: number) => Promise<void>` | Approve a named agent key (async, triggers wallet prompt) |
|
|
314
|
+
| `logout` | `() => void` | Clear all auth state and localStorage |
|
|
315
|
+
| `agent` | `StoredAgent \| null` | Current agent key (EOA flow only) |
|
|
316
|
+
| `agentReady` | `boolean` | Whether the exchange client can sign (true for TG, or EOA+agent) |
|
|
317
|
+
| `clearAgent` | `() => void` | Remove the agent key from state and storage |
|
|
318
|
+
| `botId` | `string` | Telegram bot ID from config |
|
|
319
|
+
| `authDataMap` | `Record<string, string>` | Raw Telegram auth data as key-value pairs |
|
|
320
|
+
| `telegramClient` | `TelegramClient` | Low-level gRPC client for the Telegram service |
|
|
321
|
+
| `staticClient` | `StaticClient` | Low-level gRPC client for the Static service |
|
|
228
322
|
|
|
229
323
|
### Types
|
|
230
324
|
|
|
@@ -268,6 +362,34 @@ interface StoredAgent {
|
|
|
268
362
|
privateKey: `0x${string}`;
|
|
269
363
|
address: `0x${string}`;
|
|
270
364
|
approvedAt: number; // Timestamp when approved on-chain
|
|
365
|
+
validUntil: number; // Epoch ms from extraAgents; agent is invalid after this
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
#### `HyperliquidWallet`
|
|
370
|
+
|
|
371
|
+
Re-exported from `hypurr-grpc`:
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
interface HyperliquidWallet {
|
|
375
|
+
id: number;
|
|
376
|
+
name: string;
|
|
377
|
+
ethereumAddress: string;
|
|
378
|
+
isAgent: boolean;
|
|
379
|
+
isReadOnly: boolean;
|
|
380
|
+
// ... plus balances, movements, sessions
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
#### `TelegramChatWalletPack`
|
|
385
|
+
|
|
386
|
+
Re-exported from `hypurr-grpc`:
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
interface TelegramChatWalletPack {
|
|
390
|
+
id: number;
|
|
391
|
+
telegramChatId: number;
|
|
392
|
+
name: string;
|
|
271
393
|
}
|
|
272
394
|
```
|
|
273
395
|
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ import { ReactNode } from 'react';
|
|
|
3
3
|
import { ExchangeClient, IRequestTransport } from '@hfunlabs/hyperliquid';
|
|
4
4
|
import { StaticClient } from 'hypurr-grpc/ts/hypurr/static/static_service.client';
|
|
5
5
|
import { TelegramClient } from 'hypurr-grpc/ts/hypurr/telegram/telegram_service.client';
|
|
6
|
+
import { HyperliquidWallet } from 'hypurr-grpc/ts/hypurr/wallet';
|
|
7
|
+
export { HyperliquidWallet } from 'hypurr-grpc/ts/hypurr/wallet';
|
|
8
|
+
import { TelegramChatWalletPack } from 'hypurr-grpc/ts/hypurr/user';
|
|
9
|
+
export { TelegramChatWalletPack } from 'hypurr-grpc/ts/hypurr/user';
|
|
6
10
|
|
|
7
11
|
interface HypurrConnectConfig {
|
|
8
12
|
grpcTimeout?: number;
|
|
@@ -29,11 +33,15 @@ interface HypurrUser {
|
|
|
29
33
|
photoUrl?: string;
|
|
30
34
|
authMethod: AuthMethod;
|
|
31
35
|
telegramId?: string;
|
|
36
|
+
hfunScore?: number;
|
|
37
|
+
reputationScore?: number;
|
|
32
38
|
}
|
|
33
39
|
interface StoredAgent {
|
|
34
40
|
privateKey: `0x${string}`;
|
|
35
41
|
address: `0x${string}`;
|
|
36
42
|
approvedAt: number;
|
|
43
|
+
/** Epoch ms from the `extraAgents` response; agent is invalid after this time. */
|
|
44
|
+
validUntil: number;
|
|
37
45
|
}
|
|
38
46
|
type SignTypedDataFn = (params: {
|
|
39
47
|
domain: Record<string, unknown>;
|
|
@@ -51,18 +59,36 @@ interface HypurrConnectState {
|
|
|
51
59
|
error: string | null;
|
|
52
60
|
authMethod: AuthMethod;
|
|
53
61
|
exchange: ExchangeClient<any> | null;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
wallets: HyperliquidWallet[];
|
|
63
|
+
selectedWalletId: number;
|
|
64
|
+
selectWallet: (walletId: number) => void;
|
|
65
|
+
createWallet: (name: string) => Promise<HyperliquidWallet>;
|
|
66
|
+
deleteWallet: (walletId: number) => Promise<void>;
|
|
67
|
+
refreshWallets: () => void;
|
|
68
|
+
packs: TelegramChatWalletPack[];
|
|
69
|
+
createWalletPack: (name: string) => Promise<number>;
|
|
70
|
+
addPackLabel: (params: {
|
|
71
|
+
walletAddress: string;
|
|
72
|
+
walletLabel: string;
|
|
73
|
+
packId: number;
|
|
74
|
+
}) => Promise<void>;
|
|
75
|
+
modifyPackLabel: (params: {
|
|
76
|
+
walletLabelOld: string;
|
|
77
|
+
walletLabelNew: string;
|
|
78
|
+
packId: number;
|
|
79
|
+
}) => Promise<void>;
|
|
80
|
+
removePackLabel: (params: {
|
|
81
|
+
walletLabel: string;
|
|
82
|
+
packId: number;
|
|
83
|
+
}) => Promise<void>;
|
|
57
84
|
loginModalOpen: boolean;
|
|
58
85
|
openLoginModal: () => void;
|
|
59
86
|
closeLoginModal: () => void;
|
|
60
|
-
|
|
61
|
-
|
|
87
|
+
connectEoa: (address: `0x${string}`) => void;
|
|
88
|
+
approveAgent: (signTypedDataAsync: SignTypedDataFn, chainId: number) => Promise<void>;
|
|
62
89
|
logout: () => void;
|
|
63
90
|
agent: StoredAgent | null;
|
|
64
91
|
agentReady: boolean;
|
|
65
|
-
approveAgent: (signTypedDataAsync: SignTypedDataFn) => Promise<void>;
|
|
66
92
|
clearAgent: () => void;
|
|
67
93
|
botId: string;
|
|
68
94
|
authDataMap: Record<string, string>;
|