@hfunlabs/hypurr-connect 0.1.11 → 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 +27 -2
- package/dist/index.d.ts +174 -10
- package/dist/index.js +2732 -23
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/src/DeleteWalletModal.tsx +344 -0
- package/src/HypurrConnectProvider.tsx +205 -41
- package/src/RenameWalletModal.tsx +325 -0
- package/src/UserProfileModal.tsx +982 -0
- package/src/WalletSelectorDropdown.tsx +797 -0
- package/src/agent.ts +2 -2
- package/src/icons/lucide.tsx +197 -0
- package/src/index.ts +16 -0
- package/src/privateKeySigner.ts +32 -0
- package/src/profileStyles.ts +213 -0
- package/src/types.ts +48 -10
- package/src/TelegramLoginWidget.tsx +0 -62
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { AnimatePresence, motion } from "framer-motion";
|
|
2
|
+
import type { HyperliquidWallet } from "hypurr-grpc/ts/hypurr/wallet";
|
|
3
|
+
import {
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useState,
|
|
7
|
+
type CSSProperties,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from "react";
|
|
10
|
+
import {
|
|
11
|
+
AlertTriangle,
|
|
12
|
+
Loader2,
|
|
13
|
+
Pencil,
|
|
14
|
+
SpinKeyframes,
|
|
15
|
+
X,
|
|
16
|
+
} from "./icons/lucide";
|
|
17
|
+
import {
|
|
18
|
+
closeBtnStyle as makeCloseBtnStyle,
|
|
19
|
+
fontFamily,
|
|
20
|
+
modalBackdropStyle,
|
|
21
|
+
modalHeaderStyle,
|
|
22
|
+
modalPanelStyle,
|
|
23
|
+
modalWrapperStyle,
|
|
24
|
+
profileColors,
|
|
25
|
+
raisedButtonStyle,
|
|
26
|
+
titleStyle,
|
|
27
|
+
upperLabelStyle,
|
|
28
|
+
} from "./profileStyles";
|
|
29
|
+
|
|
30
|
+
export interface RenameWalletModalProps {
|
|
31
|
+
isOpen: boolean;
|
|
32
|
+
onClose: () => void;
|
|
33
|
+
wallet: HyperliquidWallet | null;
|
|
34
|
+
onConfirm: (walletId: number, name: string) => Promise<void>;
|
|
35
|
+
onNotify?: (n: { type: "success" | "error"; message: string }) => void;
|
|
36
|
+
/** Kept for compatibility. Profile modal actions now use the shared app raised button style. */
|
|
37
|
+
accentColor?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const DANGER_BORDER = "rgba(248,113,113,0.2)";
|
|
41
|
+
const WALLET_NAME_REGEX = /^[a-zA-Z0-9/]+$/;
|
|
42
|
+
|
|
43
|
+
const backdropStyle = modalBackdropStyle(110);
|
|
44
|
+
const wrapperStyle = modalWrapperStyle(111, 16);
|
|
45
|
+
const panelStyle: CSSProperties = {
|
|
46
|
+
...modalPanelStyle(true),
|
|
47
|
+
border: `1px solid ${profileColors.surfaceBd}`,
|
|
48
|
+
};
|
|
49
|
+
const headerStyle: CSSProperties = {
|
|
50
|
+
...modalHeaderStyle,
|
|
51
|
+
borderBottom: "1px solid rgba(255,255,255,0.06)",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const bodyStyle: CSSProperties = {
|
|
55
|
+
padding: "20px 24px",
|
|
56
|
+
display: "flex",
|
|
57
|
+
flexDirection: "column",
|
|
58
|
+
gap: 16,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const infoBoxStyle: CSSProperties = {
|
|
62
|
+
padding: "10px 12px",
|
|
63
|
+
background: "rgba(255,255,255,0.03)",
|
|
64
|
+
border: "1px solid rgba(255,255,255,0.06)",
|
|
65
|
+
borderRadius: 8,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const labelStyle: CSSProperties = {
|
|
69
|
+
display: "block",
|
|
70
|
+
fontSize: 12.5,
|
|
71
|
+
lineHeight: "1rem",
|
|
72
|
+
color: profileColors.muted,
|
|
73
|
+
marginBottom: 8,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const inputStyle = (hasError: boolean, disabled: boolean): CSSProperties => ({
|
|
77
|
+
width: "100%",
|
|
78
|
+
background: "rgba(13,18,25,0.9)",
|
|
79
|
+
border: `1px solid ${
|
|
80
|
+
hasError ? "rgba(248,113,113,0.5)" : profileColors.surfaceBd
|
|
81
|
+
}`,
|
|
82
|
+
borderRadius: 8,
|
|
83
|
+
padding: "10px 12px",
|
|
84
|
+
color: profileColors.text,
|
|
85
|
+
fontFamily: fontFamily.mono,
|
|
86
|
+
fontSize: 12.5,
|
|
87
|
+
lineHeight: "1rem",
|
|
88
|
+
outline: "none",
|
|
89
|
+
opacity: disabled ? 0.5 : 1,
|
|
90
|
+
boxSizing: "border-box",
|
|
91
|
+
transition: "border-color 150ms, background-color 150ms",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const footerStyle: CSSProperties = {
|
|
95
|
+
padding: "0 24px 24px",
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const saveButtonStyle = (
|
|
99
|
+
enabled: boolean,
|
|
100
|
+
hovered: boolean,
|
|
101
|
+
): CSSProperties => ({
|
|
102
|
+
...raisedButtonStyle(enabled ? (hovered ? "hover" : "default") : "disabled"),
|
|
103
|
+
width: "100%",
|
|
104
|
+
padding: "8px 0",
|
|
105
|
+
borderRadius: 8,
|
|
106
|
+
fontSize: 12.5,
|
|
107
|
+
lineHeight: "1rem",
|
|
108
|
+
fontWeight: 500,
|
|
109
|
+
display: "flex",
|
|
110
|
+
alignItems: "center",
|
|
111
|
+
justifyContent: "center",
|
|
112
|
+
gap: 8,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
export function RenameWalletModal({
|
|
116
|
+
isOpen,
|
|
117
|
+
onClose,
|
|
118
|
+
wallet,
|
|
119
|
+
onConfirm,
|
|
120
|
+
onNotify,
|
|
121
|
+
}: RenameWalletModalProps): ReactNode {
|
|
122
|
+
const [name, setName] = useState("");
|
|
123
|
+
const [isRenaming, setIsRenaming] = useState(false);
|
|
124
|
+
const [error, setError] = useState<string | null>(null);
|
|
125
|
+
const [saveHovered, setSaveHovered] = useState(false);
|
|
126
|
+
|
|
127
|
+
const currentName = wallet?.name || "Unnamed";
|
|
128
|
+
const trimmedName = name.trim();
|
|
129
|
+
const isNameChanged = trimmedName !== currentName;
|
|
130
|
+
const isNameValid = WALLET_NAME_REGEX.test(trimmedName);
|
|
131
|
+
const canSubmit = isNameValid && isNameChanged && !isRenaming;
|
|
132
|
+
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
if (!wallet) return;
|
|
135
|
+
setName(wallet.name || "");
|
|
136
|
+
setError(null);
|
|
137
|
+
}, [wallet]);
|
|
138
|
+
|
|
139
|
+
const handleClose = useCallback(() => {
|
|
140
|
+
if (isRenaming) return;
|
|
141
|
+
setError(null);
|
|
142
|
+
onClose();
|
|
143
|
+
}, [isRenaming, onClose]);
|
|
144
|
+
|
|
145
|
+
const handleRename = useCallback(async () => {
|
|
146
|
+
if (!wallet || !isNameValid || !isNameChanged) return;
|
|
147
|
+
setError(null);
|
|
148
|
+
setIsRenaming(true);
|
|
149
|
+
try {
|
|
150
|
+
await onConfirm(wallet.id, trimmedName);
|
|
151
|
+
onNotify?.({ type: "success", message: "Wallet renamed successfully" });
|
|
152
|
+
onClose();
|
|
153
|
+
} catch (e: unknown) {
|
|
154
|
+
setError(e instanceof Error ? e.message : "Failed to rename wallet");
|
|
155
|
+
} finally {
|
|
156
|
+
setIsRenaming(false);
|
|
157
|
+
}
|
|
158
|
+
}, [
|
|
159
|
+
wallet,
|
|
160
|
+
isNameValid,
|
|
161
|
+
isNameChanged,
|
|
162
|
+
onConfirm,
|
|
163
|
+
trimmedName,
|
|
164
|
+
onClose,
|
|
165
|
+
onNotify,
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<AnimatePresence>
|
|
170
|
+
{isOpen && wallet && (
|
|
171
|
+
<>
|
|
172
|
+
<SpinKeyframes />
|
|
173
|
+
<motion.div
|
|
174
|
+
key="backdrop"
|
|
175
|
+
style={backdropStyle}
|
|
176
|
+
initial={{ opacity: 0 }}
|
|
177
|
+
animate={{ opacity: 1 }}
|
|
178
|
+
exit={{ opacity: 0 }}
|
|
179
|
+
transition={{ duration: 0.15 }}
|
|
180
|
+
onClick={handleClose}
|
|
181
|
+
/>
|
|
182
|
+
<div style={wrapperStyle}>
|
|
183
|
+
<motion.div
|
|
184
|
+
key="panel"
|
|
185
|
+
style={panelStyle}
|
|
186
|
+
initial={{ opacity: 0, y: 8 }}
|
|
187
|
+
animate={{ opacity: 1, y: 0 }}
|
|
188
|
+
exit={{ opacity: 0, y: 8 }}
|
|
189
|
+
transition={{ duration: 0.18, ease: "easeOut" }}
|
|
190
|
+
onClick={(e) => e.stopPropagation()}
|
|
191
|
+
>
|
|
192
|
+
<div style={headerStyle}>
|
|
193
|
+
<h3 style={titleStyle}>Rename Wallet</h3>
|
|
194
|
+
<button
|
|
195
|
+
onClick={handleClose}
|
|
196
|
+
disabled={isRenaming}
|
|
197
|
+
style={makeCloseBtnStyle(isRenaming)}
|
|
198
|
+
aria-label="Close"
|
|
199
|
+
>
|
|
200
|
+
<X size={16} />
|
|
201
|
+
</button>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<div style={bodyStyle}>
|
|
205
|
+
<div style={infoBoxStyle}>
|
|
206
|
+
<p
|
|
207
|
+
style={{
|
|
208
|
+
margin: "0 0 4px",
|
|
209
|
+
color: profileColors.muted,
|
|
210
|
+
...upperLabelStyle,
|
|
211
|
+
}}
|
|
212
|
+
>
|
|
213
|
+
Wallet
|
|
214
|
+
</p>
|
|
215
|
+
<p
|
|
216
|
+
style={{
|
|
217
|
+
margin: 0,
|
|
218
|
+
fontSize: 12.5,
|
|
219
|
+
lineHeight: "1rem",
|
|
220
|
+
fontWeight: 500,
|
|
221
|
+
color: profileColors.text,
|
|
222
|
+
}}
|
|
223
|
+
>
|
|
224
|
+
{currentName}
|
|
225
|
+
</p>
|
|
226
|
+
<p
|
|
227
|
+
style={{
|
|
228
|
+
margin: "2px 0 0",
|
|
229
|
+
fontSize: 12.5,
|
|
230
|
+
lineHeight: "1rem",
|
|
231
|
+
color: profileColors.muted,
|
|
232
|
+
fontFamily: fontFamily.mono,
|
|
233
|
+
wordBreak: "break-all",
|
|
234
|
+
}}
|
|
235
|
+
>
|
|
236
|
+
{wallet.ethereumAddress}
|
|
237
|
+
</p>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div>
|
|
241
|
+
<label style={labelStyle}>Wallet name</label>
|
|
242
|
+
<input
|
|
243
|
+
type="text"
|
|
244
|
+
value={name}
|
|
245
|
+
onChange={(e) => {
|
|
246
|
+
setName(e.target.value);
|
|
247
|
+
setError(null);
|
|
248
|
+
}}
|
|
249
|
+
onKeyDown={(e) => {
|
|
250
|
+
if (e.key === "Enter") handleRename();
|
|
251
|
+
if (e.key === "Escape") handleClose();
|
|
252
|
+
}}
|
|
253
|
+
disabled={isRenaming}
|
|
254
|
+
autoFocus
|
|
255
|
+
style={inputStyle(!!error, isRenaming)}
|
|
256
|
+
/>
|
|
257
|
+
<p
|
|
258
|
+
style={{
|
|
259
|
+
margin: "8px 0 0",
|
|
260
|
+
fontSize: 12.5,
|
|
261
|
+
lineHeight: "1rem",
|
|
262
|
+
color: profileColors.subdued,
|
|
263
|
+
}}
|
|
264
|
+
>
|
|
265
|
+
Use letters, numbers, and / only.
|
|
266
|
+
</p>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
{error && (
|
|
270
|
+
<div
|
|
271
|
+
style={{
|
|
272
|
+
display: "flex",
|
|
273
|
+
alignItems: "flex-start",
|
|
274
|
+
gap: 8,
|
|
275
|
+
padding: 12,
|
|
276
|
+
background: "rgba(248,113,113,0.08)",
|
|
277
|
+
border: `1px solid ${DANGER_BORDER}`,
|
|
278
|
+
borderRadius: 8,
|
|
279
|
+
}}
|
|
280
|
+
>
|
|
281
|
+
<AlertTriangle
|
|
282
|
+
size={14}
|
|
283
|
+
color={profileColors.danger}
|
|
284
|
+
style={{ flexShrink: 0, marginTop: 2 }}
|
|
285
|
+
/>
|
|
286
|
+
<p
|
|
287
|
+
style={{
|
|
288
|
+
margin: 0,
|
|
289
|
+
fontSize: 12.5,
|
|
290
|
+
lineHeight: "1rem",
|
|
291
|
+
color: profileColors.danger,
|
|
292
|
+
}}
|
|
293
|
+
>
|
|
294
|
+
{error}
|
|
295
|
+
</p>
|
|
296
|
+
</div>
|
|
297
|
+
)}
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
<div style={footerStyle}>
|
|
301
|
+
<button
|
|
302
|
+
onClick={handleRename}
|
|
303
|
+
disabled={!canSubmit}
|
|
304
|
+
onMouseEnter={() => setSaveHovered(true)}
|
|
305
|
+
onMouseLeave={() => setSaveHovered(false)}
|
|
306
|
+
style={saveButtonStyle(canSubmit, saveHovered)}
|
|
307
|
+
>
|
|
308
|
+
{isRenaming ? (
|
|
309
|
+
<>
|
|
310
|
+
<Loader2 size={14} /> Saving...
|
|
311
|
+
</>
|
|
312
|
+
) : (
|
|
313
|
+
<>
|
|
314
|
+
<Pencil size={14} /> Save Name
|
|
315
|
+
</>
|
|
316
|
+
)}
|
|
317
|
+
</button>
|
|
318
|
+
</div>
|
|
319
|
+
</motion.div>
|
|
320
|
+
</div>
|
|
321
|
+
</>
|
|
322
|
+
)}
|
|
323
|
+
</AnimatePresence>
|
|
324
|
+
);
|
|
325
|
+
}
|