@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/README.md +0 -1
- package/dist/index.d.ts +128 -9
- package/dist/index.js +2568 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/DeleteWalletModal.tsx +344 -0
- package/src/HypurrConnectProvider.tsx +29 -18
- package/src/RenameWalletModal.tsx +325 -0
- package/src/UserProfileModal.tsx +982 -0
- package/src/WalletSelectorDropdown.tsx +797 -0
- package/src/icons/lucide.tsx +197 -0
- package/src/index.ts +9 -0
- package/src/profileStyles.ts +213 -0
- package/src/types.ts +4 -10
- package/src/TelegramLoginWidget.tsx +0 -62
package/package.json
CHANGED
|
@@ -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
|
+
"{walletName}"
|
|
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:
|
|
508
|
-
|
|
509
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1172
|
-
config.telegram
|
|
1173
|
-
config.telegram
|
|
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,
|