@hfunlabs/hypurr-connect 0.1.12 → 0.1.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hfunlabs/hypurr-connect",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -0,0 +1,344 @@
1
+ import { AnimatePresence, motion } from "framer-motion";
2
+ import type { HyperliquidWallet } from "hypurr-grpc/ts/hypurr/wallet";
3
+ import {
4
+ useCallback,
5
+ useState,
6
+ type CSSProperties,
7
+ type ReactNode,
8
+ } from "react";
9
+ import {
10
+ AlertTriangle,
11
+ Loader2,
12
+ SpinKeyframes,
13
+ Trash2,
14
+ X,
15
+ } from "./icons/lucide";
16
+ import {
17
+ closeBtnStyle as makeCloseBtnStyle,
18
+ dangerOutlineButtonStyle,
19
+ fontFamily,
20
+ modalBackdropStyle,
21
+ modalHeaderStyle,
22
+ modalPanelStyle,
23
+ modalWrapperStyle,
24
+ profileColors,
25
+ titleStyle,
26
+ upperLabelStyle,
27
+ } from "./profileStyles";
28
+
29
+ export interface DeleteWalletModalProps {
30
+ isOpen: boolean;
31
+ onClose: () => void;
32
+ wallet: HyperliquidWallet | null;
33
+ onConfirm: (walletId: number) => Promise<void>;
34
+ /** Optional toast callback. Fires `{type:"success"}` on delete; errors are shown inline. */
35
+ onNotify?: (n: { type: "success" | "error"; message: string }) => void;
36
+ }
37
+
38
+ const DANGER_BG = "rgba(248,113,113,0.07)";
39
+ const DANGER_BORDER = "rgba(248,113,113,0.2)";
40
+
41
+ const backdropStyle = modalBackdropStyle(110);
42
+ const wrapperStyle = modalWrapperStyle(111, 16);
43
+ const panelStyle: CSSProperties = {
44
+ ...modalPanelStyle(true),
45
+ border: `1px solid ${profileColors.surfaceBd}`,
46
+ };
47
+ const headerStyle: CSSProperties = {
48
+ ...modalHeaderStyle,
49
+ borderBottom: "1px solid rgba(255,255,255,0.06)",
50
+ };
51
+
52
+ const bodyStyle: CSSProperties = {
53
+ padding: "20px 24px",
54
+ display: "flex",
55
+ flexDirection: "column",
56
+ gap: 16,
57
+ };
58
+
59
+ const warningBoxStyle: CSSProperties = {
60
+ display: "flex",
61
+ alignItems: "flex-start",
62
+ gap: 12,
63
+ padding: 14,
64
+ background: DANGER_BG,
65
+ border: `1px solid ${DANGER_BORDER}`,
66
+ borderRadius: 8,
67
+ };
68
+
69
+ const infoBoxStyle: CSSProperties = {
70
+ padding: "10px 12px",
71
+ background: "rgba(255,255,255,0.03)",
72
+ border: "1px solid rgba(255,255,255,0.06)",
73
+ borderRadius: 8,
74
+ };
75
+
76
+ const labelStyle: CSSProperties = {
77
+ display: "block",
78
+ fontSize: 12.5,
79
+ lineHeight: "1rem",
80
+ color: profileColors.muted,
81
+ marginBottom: 8,
82
+ };
83
+
84
+ const inputStyle = (disabled: boolean): CSSProperties => ({
85
+ width: "100%",
86
+ background: "rgba(13,18,25,0.9)",
87
+ border: `1px solid ${profileColors.surfaceBd}`,
88
+ borderRadius: 8,
89
+ padding: "10px 12px",
90
+ color: profileColors.text,
91
+ fontFamily: fontFamily.mono,
92
+ fontSize: 12.5,
93
+ lineHeight: "1rem",
94
+ outline: "none",
95
+ opacity: disabled ? 0.5 : 1,
96
+ boxSizing: "border-box",
97
+ transition: "border-color 150ms, background-color 150ms",
98
+ });
99
+
100
+ const footerStyle: CSSProperties = {
101
+ padding: "0 24px 24px",
102
+ };
103
+
104
+ const deleteButtonStyle = (
105
+ enabled: boolean,
106
+ hovered: boolean,
107
+ ): CSSProperties => ({
108
+ ...dangerOutlineButtonStyle(enabled, hovered),
109
+ width: "100%",
110
+ padding: "8px 0",
111
+ borderRadius: 8,
112
+ fontSize: 12.5,
113
+ lineHeight: "1rem",
114
+ fontWeight: 500,
115
+ display: "flex",
116
+ alignItems: "center",
117
+ justifyContent: "center",
118
+ gap: 8,
119
+ transition: "background-color 150ms, color 150ms, border-color 150ms",
120
+ });
121
+
122
+ export function DeleteWalletModal({
123
+ isOpen,
124
+ onClose,
125
+ wallet,
126
+ onConfirm,
127
+ onNotify,
128
+ }: DeleteWalletModalProps): ReactNode {
129
+ const [confirmName, setConfirmName] = useState("");
130
+ const [isDeleting, setIsDeleting] = useState(false);
131
+ const [error, setError] = useState<string | null>(null);
132
+ const [deleteHovered, setDeleteHovered] = useState(false);
133
+
134
+ const walletName = wallet?.name || "Unnamed Wallet";
135
+ const isNameMatch = confirmName === walletName;
136
+ const canDelete = isNameMatch && !isDeleting;
137
+
138
+ const handleClose = useCallback(() => {
139
+ if (isDeleting) return;
140
+ setConfirmName("");
141
+ setError(null);
142
+ onClose();
143
+ }, [isDeleting, onClose]);
144
+
145
+ const handleDelete = useCallback(async () => {
146
+ if (!wallet || !isNameMatch) return;
147
+ setError(null);
148
+ setIsDeleting(true);
149
+ try {
150
+ await onConfirm(wallet.id);
151
+ setConfirmName("");
152
+ onNotify?.({ type: "success", message: "Wallet deleted successfully" });
153
+ onClose();
154
+ } catch (e: unknown) {
155
+ setError(e instanceof Error ? e.message : "Failed to delete wallet");
156
+ } finally {
157
+ setIsDeleting(false);
158
+ }
159
+ }, [wallet, isNameMatch, onConfirm, onClose, onNotify]);
160
+
161
+ return (
162
+ <AnimatePresence>
163
+ {isOpen && wallet && (
164
+ <>
165
+ <SpinKeyframes />
166
+ <motion.div
167
+ key="backdrop"
168
+ style={backdropStyle}
169
+ initial={{ opacity: 0 }}
170
+ animate={{ opacity: 1 }}
171
+ exit={{ opacity: 0 }}
172
+ transition={{ duration: 0.15 }}
173
+ onClick={handleClose}
174
+ />
175
+ <div style={wrapperStyle}>
176
+ <motion.div
177
+ key="panel"
178
+ style={panelStyle}
179
+ initial={{ opacity: 0, y: 8 }}
180
+ animate={{ opacity: 1, y: 0 }}
181
+ exit={{ opacity: 0, y: 8 }}
182
+ transition={{ duration: 0.18, ease: "easeOut" }}
183
+ onClick={(e) => e.stopPropagation()}
184
+ >
185
+ <div style={headerStyle}>
186
+ <h3 style={titleStyle}>Delete Wallet</h3>
187
+ <button
188
+ onClick={handleClose}
189
+ disabled={isDeleting}
190
+ style={makeCloseBtnStyle(isDeleting)}
191
+ aria-label="Close"
192
+ >
193
+ <X size={16} />
194
+ </button>
195
+ </div>
196
+
197
+ <div style={bodyStyle}>
198
+ <div style={warningBoxStyle}>
199
+ <AlertTriangle
200
+ size={15}
201
+ color={profileColors.danger}
202
+ style={{ flexShrink: 0, marginTop: 2 }}
203
+ />
204
+ <div>
205
+ <p
206
+ style={{
207
+ margin: 0,
208
+ fontSize: 12.5,
209
+ lineHeight: "1rem",
210
+ color: profileColors.danger,
211
+ fontWeight: 500,
212
+ }}
213
+ >
214
+ This action cannot be undone
215
+ </p>
216
+ <p
217
+ style={{
218
+ margin: "2px 0 0",
219
+ fontSize: 12.5,
220
+ lineHeight: "1rem",
221
+ color: "rgba(248,113,113,0.7)",
222
+ }}
223
+ >
224
+ The private key will be permanently deleted. Any remaining
225
+ funds will be inaccessible.
226
+ </p>
227
+ </div>
228
+ </div>
229
+
230
+ <div style={infoBoxStyle}>
231
+ <p
232
+ style={{
233
+ margin: "0 0 4px",
234
+ color: profileColors.muted,
235
+ ...upperLabelStyle,
236
+ }}
237
+ >
238
+ Wallet to delete
239
+ </p>
240
+ <p
241
+ style={{
242
+ margin: 0,
243
+ fontSize: 12.5,
244
+ lineHeight: "1rem",
245
+ fontWeight: 500,
246
+ color: profileColors.text,
247
+ }}
248
+ >
249
+ {walletName}
250
+ </p>
251
+ <p
252
+ style={{
253
+ margin: "2px 0 0",
254
+ fontSize: 12.5,
255
+ lineHeight: "1rem",
256
+ color: profileColors.muted,
257
+ fontFamily: fontFamily.mono,
258
+ wordBreak: "break-all",
259
+ }}
260
+ >
261
+ {wallet.ethereumAddress}
262
+ </p>
263
+ </div>
264
+
265
+ <div>
266
+ <label style={labelStyle}>
267
+ Type{" "}
268
+ <span
269
+ style={{
270
+ color: profileColors.text,
271
+ fontWeight: 500,
272
+ }}
273
+ >
274
+ &quot;{walletName}&quot;
275
+ </span>{" "}
276
+ to confirm
277
+ </label>
278
+ <input
279
+ type="text"
280
+ value={confirmName}
281
+ onChange={(e) => setConfirmName(e.target.value)}
282
+ placeholder={walletName}
283
+ disabled={isDeleting}
284
+ style={inputStyle(isDeleting)}
285
+ />
286
+ </div>
287
+
288
+ {error && (
289
+ <div
290
+ style={{
291
+ display: "flex",
292
+ alignItems: "flex-start",
293
+ gap: 8,
294
+ padding: 12,
295
+ background: "rgba(248,113,113,0.08)",
296
+ border: `1px solid ${DANGER_BORDER}`,
297
+ borderRadius: 8,
298
+ }}
299
+ >
300
+ <AlertTriangle
301
+ size={14}
302
+ color={profileColors.danger}
303
+ style={{ flexShrink: 0, marginTop: 2 }}
304
+ />
305
+ <p
306
+ style={{
307
+ margin: 0,
308
+ fontSize: 12.5,
309
+ lineHeight: "1rem",
310
+ color: profileColors.danger,
311
+ }}
312
+ >
313
+ {error}
314
+ </p>
315
+ </div>
316
+ )}
317
+ </div>
318
+
319
+ <div style={footerStyle}>
320
+ <button
321
+ onClick={handleDelete}
322
+ disabled={!canDelete}
323
+ onMouseEnter={() => setDeleteHovered(true)}
324
+ onMouseLeave={() => setDeleteHovered(false)}
325
+ style={deleteButtonStyle(canDelete, deleteHovered)}
326
+ >
327
+ {isDeleting ? (
328
+ <>
329
+ <Loader2 size={14} /> Deleting...
330
+ </>
331
+ ) : (
332
+ <>
333
+ <Trash2 size={14} /> Delete Wallet
334
+ </>
335
+ )}
336
+ </button>
337
+ </div>
338
+ </motion.div>
339
+ </div>
340
+ </>
341
+ )}
342
+ </AnimatePresence>
343
+ );
344
+ }
@@ -52,8 +52,6 @@ import type {
52
52
  /** @internal context value — extends the public type with fields used only by library internals */
53
53
  interface InternalConnectState extends HypurrConnectState {
54
54
  loginTelegram: () => void;
55
- botUsername: string;
56
- useWidget: boolean;
57
55
  }
58
56
 
59
57
  const TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-jwt";
@@ -504,9 +502,12 @@ export function HypurrConnectProvider({
504
502
  displayName: tgUser.telegramUsername
505
503
  ? `@${tgUser.telegramUsername}`
506
504
  : `Telegram ${tgUser.telegramId}`,
507
- photoUrl: tgUser.pictureFileId
508
- ? `${mediaUrl}/${tgUser.pictureFileId}`
509
- : undefined,
505
+ photoUrl: (() => {
506
+ // `pictureFileId` is read at runtime; the schema may not type it.
507
+ const fileId = (tgUser as unknown as { pictureFileId?: string })
508
+ .pictureFileId;
509
+ return fileId ? `${mediaUrl}/${fileId}` : undefined;
510
+ })(),
510
511
  authMethod: "telegram",
511
512
  telegramId: String(tgUser.telegramId),
512
513
  hfunScore: tgUser?.reputation?.hfunScore,
@@ -912,6 +913,21 @@ export function HypurrConnectProvider({
912
913
  [tgClient, telegramRpcOptions, selectedWalletId, wallets, refreshWallets],
913
914
  );
914
915
 
916
+ const renameWallet = useCallback(
917
+ async (walletId: number, name: string): Promise<void> => {
918
+ await tgClient.hyperliquidWalletUpdate(
919
+ {
920
+ authData: {},
921
+ walletId,
922
+ name,
923
+ },
924
+ telegramRpcOptions,
925
+ );
926
+ refreshWallets();
927
+ },
928
+ [tgClient, telegramRpcOptions, refreshWallets],
929
+ );
930
+
915
931
  const createWalletPack = useCallback(
916
932
  async (name: string): Promise<number> => {
917
933
  const { response } = await tgClient.telegramChatWalletPackCreate(
@@ -1133,16 +1149,16 @@ export function HypurrConnectProvider({
1133
1149
  const state = randomState();
1134
1150
  sessionStorage.setItem(TELEGRAM_AUTH_STATE_KEY, state);
1135
1151
 
1136
- const configuredReturnTo = config.telegram.returnTo;
1152
+ const configuredReturnTo = config.telegram?.returnTo;
1137
1153
  const returnTo =
1138
1154
  typeof configuredReturnTo === "function"
1139
1155
  ? configuredReturnTo()
1140
1156
  : configuredReturnTo || currentReturnTo();
1141
1157
 
1142
- const authUrl = new URL(config.telegram.authHubUrl || DEFAULT_AUTH_HUB_URL);
1158
+ const authUrl = new URL(config.telegram?.authHubUrl || DEFAULT_AUTH_HUB_URL);
1143
1159
  authUrl.searchParams.set("return_to", returnTo);
1144
1160
  authUrl.searchParams.set("state", state);
1145
- authUrl.searchParams.set("scope", normalizeScopes(config.telegram.scope));
1161
+ authUrl.searchParams.set("scope", normalizeScopes(config.telegram?.scope));
1146
1162
 
1147
1163
  const width = 520;
1148
1164
  const height = 720;
@@ -1168,9 +1184,9 @@ export function HypurrConnectProvider({
1168
1184
 
1169
1185
  window.location.assign(authUrl.toString());
1170
1186
  }, [
1171
- config.telegram.authHubUrl,
1172
- config.telegram.returnTo,
1173
- config.telegram.scope,
1187
+ config.telegram?.authHubUrl,
1188
+ config.telegram?.returnTo,
1189
+ config.telegram?.scope,
1174
1190
  ]);
1175
1191
 
1176
1192
  const connectEoa = useCallback(
@@ -1333,6 +1349,7 @@ export function HypurrConnectProvider({
1333
1349
 
1334
1350
  createWallet,
1335
1351
  deleteWallet,
1352
+ renameWallet,
1336
1353
  refreshWallets,
1337
1354
 
1338
1355
  packs,
@@ -1362,10 +1379,6 @@ export function HypurrConnectProvider({
1362
1379
  agentReady,
1363
1380
  clearAgent: handleClearAgent,
1364
1381
 
1365
- botId: config.telegram?.botId ?? "",
1366
- botUsername: config.telegram?.botUsername ?? "",
1367
- useWidget: config.telegram?.useWidget ?? false,
1368
-
1369
1382
  authDataMap,
1370
1383
  authToken: tgAuthToken,
1371
1384
  telegramRpcOptions,
@@ -1385,6 +1398,7 @@ export function HypurrConnectProvider({
1385
1398
  selectWallet,
1386
1399
  createWallet,
1387
1400
  deleteWallet,
1401
+ renameWallet,
1388
1402
  refreshWallets,
1389
1403
  packs,
1390
1404
  createWalletPack,
@@ -1407,9 +1421,6 @@ export function HypurrConnectProvider({
1407
1421
  agent,
1408
1422
  agentReady,
1409
1423
  handleClearAgent,
1410
- config.telegram?.botId,
1411
- config.telegram?.botUsername,
1412
- config.telegram?.useWidget,
1413
1424
  authDataMap,
1414
1425
  tgAuthToken,
1415
1426
  telegramRpcOptions,