@hfunlabs/hypurr-connect 0.1.1 → 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 +306 -141
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/HypurrConnectProvider.tsx +328 -158
- package/src/LoginModal.tsx +2 -2
- package/src/agent.ts +80 -0
- package/src/index.ts +4 -0
- package/src/types.ts +38 -7
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ExchangeClient,
|
|
3
3
|
HttpTransport,
|
|
4
|
-
|
|
4
|
+
type IRequestTransport,
|
|
5
5
|
} from "@hfunlabs/hyperliquid";
|
|
6
|
+
import { approveAgent as sdkApproveAgent } from "@hfunlabs/hyperliquid/api/exchange";
|
|
6
7
|
import { PrivateKeySigner } from "@hfunlabs/hyperliquid/signing";
|
|
7
8
|
import type { TelegramUserResponse } from "hypurr-grpc/ts/hypurr/telegram/telegram_service";
|
|
8
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
TelegramUser as HypurrTelegramUser,
|
|
11
|
+
TelegramChatWalletPack,
|
|
12
|
+
} from "hypurr-grpc/ts/hypurr/user";
|
|
13
|
+
import type { HyperliquidWallet } from "hypurr-grpc/ts/hypurr/wallet";
|
|
9
14
|
import {
|
|
10
15
|
createContext,
|
|
11
16
|
useCallback,
|
|
12
17
|
useContext,
|
|
13
18
|
useEffect,
|
|
14
19
|
useMemo,
|
|
20
|
+
useRef,
|
|
15
21
|
useState,
|
|
16
22
|
type ReactNode,
|
|
17
23
|
} from "react";
|
|
18
24
|
import {
|
|
25
|
+
AGENT_NAME,
|
|
19
26
|
clearAgent as clearStoredAgent,
|
|
27
|
+
fetchActiveAgent,
|
|
20
28
|
generateAgentKey,
|
|
29
|
+
isAgentValid,
|
|
30
|
+
isDeadAgentError,
|
|
21
31
|
loadAgent,
|
|
22
32
|
saveAgent,
|
|
23
33
|
} from "./agent";
|
|
@@ -33,6 +43,11 @@ import type {
|
|
|
33
43
|
TelegramLoginData,
|
|
34
44
|
} from "./types";
|
|
35
45
|
|
|
46
|
+
/** @internal context value — extends the public type with fields used only by library internals */
|
|
47
|
+
interface InternalConnectState extends HypurrConnectState {
|
|
48
|
+
loginTelegram: (data: TelegramLoginData) => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
const TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-user";
|
|
37
52
|
|
|
38
53
|
function toAuthDataMap(data: TelegramLoginData): Record<string, string> {
|
|
@@ -48,7 +63,7 @@ function toAuthDataMap(data: TelegramLoginData): Record<string, string> {
|
|
|
48
63
|
return map;
|
|
49
64
|
}
|
|
50
65
|
|
|
51
|
-
const HypurrConnectContext = createContext<
|
|
66
|
+
const HypurrConnectContext = createContext<InternalConnectState | null>(null);
|
|
52
67
|
|
|
53
68
|
export function useHypurrConnect(): HypurrConnectState {
|
|
54
69
|
const ctx = useContext(HypurrConnectContext);
|
|
@@ -59,6 +74,16 @@ export function useHypurrConnect(): HypurrConnectState {
|
|
|
59
74
|
return ctx;
|
|
60
75
|
}
|
|
61
76
|
|
|
77
|
+
/** @internal — gives library components access to fields not on the public API */
|
|
78
|
+
export function useHypurrConnectInternal(): InternalConnectState {
|
|
79
|
+
const ctx = useContext(HypurrConnectContext);
|
|
80
|
+
if (!ctx)
|
|
81
|
+
throw new Error(
|
|
82
|
+
"useHypurrConnectInternal must be used within <HypurrConnectProvider>",
|
|
83
|
+
);
|
|
84
|
+
return ctx;
|
|
85
|
+
}
|
|
86
|
+
|
|
62
87
|
export function HypurrConnectProvider({
|
|
63
88
|
config,
|
|
64
89
|
children,
|
|
@@ -89,6 +114,8 @@ export function HypurrConnectProvider({
|
|
|
89
114
|
[tgLoginData],
|
|
90
115
|
);
|
|
91
116
|
|
|
117
|
+
const [tgUserTick, setTgUserTick] = useState(0);
|
|
118
|
+
|
|
92
119
|
useEffect(() => {
|
|
93
120
|
if (!tgLoginData) return;
|
|
94
121
|
let cancelled = false;
|
|
@@ -98,9 +125,7 @@ export function HypurrConnectProvider({
|
|
|
98
125
|
(async () => {
|
|
99
126
|
try {
|
|
100
127
|
const authData = toAuthDataMap(tgLoginData);
|
|
101
|
-
console.log(authData);
|
|
102
128
|
const { response } = await tgClient.telegramUser({ authData });
|
|
103
|
-
console.log(response);
|
|
104
129
|
if (cancelled) return;
|
|
105
130
|
setTgUser((response as TelegramUserResponse).user ?? null);
|
|
106
131
|
} catch (err) {
|
|
@@ -115,19 +140,13 @@ export function HypurrConnectProvider({
|
|
|
115
140
|
return () => {
|
|
116
141
|
cancelled = true;
|
|
117
142
|
};
|
|
118
|
-
}, [tgLoginData, tgClient]);
|
|
143
|
+
}, [tgLoginData, tgClient, tgUserTick]);
|
|
119
144
|
|
|
120
145
|
// ── EOA auth state ───────────────────────────────────────────
|
|
121
146
|
const [eoaAddress, setEoaAddress] = useState<`0x${string}` | null>(null);
|
|
122
147
|
const [agent, setAgent] = useState<StoredAgent | null>(null);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (eoaAddress) {
|
|
126
|
-
setAgent(loadAgent(eoaAddress));
|
|
127
|
-
} else {
|
|
128
|
-
setAgent(null);
|
|
129
|
-
}
|
|
130
|
-
}, [eoaAddress]);
|
|
148
|
+
const [eoaLoading, setEoaLoading] = useState(false);
|
|
149
|
+
const [eoaError, setEoaError] = useState<string | null>(null);
|
|
131
150
|
|
|
132
151
|
// ── Derived auth ─────────────────────────────────────────────
|
|
133
152
|
const authMethod: AuthMethod = tgLoginData
|
|
@@ -136,19 +155,59 @@ export function HypurrConnectProvider({
|
|
|
136
155
|
? "eoa"
|
|
137
156
|
: null;
|
|
138
157
|
|
|
139
|
-
|
|
158
|
+
// ── Multi-wallet state (Telegram) ─────────────────────────────
|
|
159
|
+
const [wallets, setWallets] = useState<HyperliquidWallet[]>([]);
|
|
160
|
+
const [selectedWalletId, setSelectedWalletId] = useState<number>(0);
|
|
161
|
+
const [packs, setPacks] = useState<TelegramChatWalletPack[]>([]);
|
|
162
|
+
|
|
163
|
+
const refreshWallets = useCallback(() => setTgUserTick((t) => t + 1), []);
|
|
164
|
+
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (authMethod !== "telegram" || !tgUser) {
|
|
167
|
+
setWallets([]);
|
|
168
|
+
setSelectedWalletId(0);
|
|
169
|
+
setPacks([]);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const userWallets = tgUser.wallets ?? [];
|
|
174
|
+
setWallets(userWallets);
|
|
175
|
+
setPacks(tgUser.packs ?? []);
|
|
176
|
+
|
|
177
|
+
const defaultId = tgUser.walletId || userWallets[0]?.id || 0;
|
|
178
|
+
setSelectedWalletId((prev) => {
|
|
179
|
+
if (prev && userWallets.some((w) => w.id === prev)) return prev;
|
|
180
|
+
return defaultId;
|
|
181
|
+
});
|
|
182
|
+
}, [authMethod, tgUser]);
|
|
183
|
+
|
|
184
|
+
const selectedWallet = useMemo(
|
|
185
|
+
() => wallets.find((w) => w.id === selectedWalletId) ?? wallets[0] ?? null,
|
|
186
|
+
[wallets, selectedWalletId],
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const selectWallet = useCallback(
|
|
190
|
+
(walletId: number) => {
|
|
191
|
+
if (wallets.some((w) => w.id === walletId)) {
|
|
192
|
+
setSelectedWalletId(walletId);
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
[wallets],
|
|
196
|
+
);
|
|
140
197
|
|
|
141
198
|
const user = useMemo<HypurrUser | null>(() => {
|
|
142
|
-
if (tgLoginData && authMethod === "telegram") {
|
|
199
|
+
if (tgLoginData && authMethod === "telegram" && selectedWallet) {
|
|
143
200
|
return {
|
|
144
|
-
address:
|
|
145
|
-
walletId:
|
|
201
|
+
address: selectedWallet.ethereumAddress,
|
|
202
|
+
walletId: selectedWallet.id,
|
|
146
203
|
displayName: tgLoginData.username
|
|
147
204
|
? `@${tgLoginData.username}`
|
|
148
205
|
: tgLoginData.first_name,
|
|
149
206
|
photoUrl: tgLoginData.photo_url,
|
|
150
207
|
authMethod: "telegram",
|
|
151
208
|
telegramId: String(tgLoginData.id),
|
|
209
|
+
hfunScore: tgUser?.reputation?.hfunScore,
|
|
210
|
+
reputationScore: tgUser?.reputation?.reputationScore,
|
|
152
211
|
};
|
|
153
212
|
}
|
|
154
213
|
if (eoaAddress && authMethod === "eoa") {
|
|
@@ -160,11 +219,24 @@ export function HypurrConnectProvider({
|
|
|
160
219
|
};
|
|
161
220
|
}
|
|
162
221
|
return null;
|
|
163
|
-
}, [tgLoginData,
|
|
222
|
+
}, [tgLoginData, selectedWallet, eoaAddress, authMethod, tgUser]);
|
|
164
223
|
|
|
165
224
|
// ── Exchange client ──────────────────────────────────────────
|
|
166
225
|
// Telegram: GrpcExchangeTransport → HyperliquidCoreAction (server signs)
|
|
167
226
|
// EOA: HttpTransport + agent wallet (SDK signs locally)
|
|
227
|
+
|
|
228
|
+
const onDeadAgentRef = useRef<((address: `0x${string}`) => void) | null>(
|
|
229
|
+
null,
|
|
230
|
+
);
|
|
231
|
+
onDeadAgentRef.current = (addr: `0x${string}`) => {
|
|
232
|
+
clearStoredAgent(addr);
|
|
233
|
+
setAgent(null);
|
|
234
|
+
setEoaError("Agent expired or was deregistered. Please reconnect.");
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const agentReady =
|
|
238
|
+
authMethod === "telegram" || (authMethod === "eoa" && !!agent);
|
|
239
|
+
|
|
168
240
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
169
241
|
const exchange = useMemo<ExchangeClient<any> | null>(() => {
|
|
170
242
|
if (authMethod === "telegram" && user?.address) {
|
|
@@ -181,150 +253,153 @@ export function HypurrConnectProvider({
|
|
|
181
253
|
});
|
|
182
254
|
}
|
|
183
255
|
|
|
184
|
-
if (authMethod === "eoa" &&
|
|
256
|
+
if (authMethod === "eoa" && eoaAddress) {
|
|
257
|
+
if (!agent) {
|
|
258
|
+
const noAgentTransport: IRequestTransport = {
|
|
259
|
+
isTestnet: config.isTestnet ?? false,
|
|
260
|
+
request(): Promise<never> {
|
|
261
|
+
throw new Error(
|
|
262
|
+
"[HypurrConnect] No agent key approved. " +
|
|
263
|
+
"Call approveAgent(signTypedDataAsync) before using the exchange client. " +
|
|
264
|
+
"This is required for EOA wallets to sign transactions on Hyperliquid.",
|
|
265
|
+
);
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
return new ExchangeClient({
|
|
269
|
+
transport: noAgentTransport,
|
|
270
|
+
externalSigning: true,
|
|
271
|
+
userAddress: eoaAddress,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const inner = new HttpTransport({
|
|
276
|
+
isTestnet: config.isTestnet ?? false,
|
|
277
|
+
});
|
|
278
|
+
const deadAgentAddr = eoaAddress;
|
|
279
|
+
const guardedTransport: IRequestTransport = {
|
|
280
|
+
isTestnet: inner.isTestnet,
|
|
281
|
+
async request<T>(
|
|
282
|
+
endpoint: "info" | "exchange" | "explorer",
|
|
283
|
+
payload: unknown,
|
|
284
|
+
signal?: AbortSignal,
|
|
285
|
+
): Promise<T> {
|
|
286
|
+
try {
|
|
287
|
+
return await inner.request<T>(endpoint, payload, signal);
|
|
288
|
+
} catch (err) {
|
|
289
|
+
if (endpoint === "exchange" && isDeadAgentError(err)) {
|
|
290
|
+
onDeadAgentRef.current?.(deadAgentAddr);
|
|
291
|
+
}
|
|
292
|
+
throw err;
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
};
|
|
185
296
|
const wallet = new PrivateKeySigner(agent.privateKey);
|
|
186
297
|
return new ExchangeClient({
|
|
187
|
-
transport:
|
|
188
|
-
isTestnet: config.isTestnet ?? false,
|
|
189
|
-
}),
|
|
298
|
+
transport: guardedTransport,
|
|
190
299
|
wallet,
|
|
191
300
|
});
|
|
192
301
|
}
|
|
193
302
|
|
|
194
303
|
return null;
|
|
195
|
-
}, [
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}),
|
|
205
|
-
[config.isTestnet],
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
const [usdcBalance, setUsdcBalance] = useState<string | null>(null);
|
|
209
|
-
const [usdcBalanceLoading, setUsdcBalanceLoading] = useState(false);
|
|
210
|
-
const [balanceTick, setBalanceTick] = useState(0);
|
|
211
|
-
|
|
212
|
-
const refreshBalance = useCallback(() => setBalanceTick((t) => t + 1), []);
|
|
304
|
+
}, [
|
|
305
|
+
authMethod,
|
|
306
|
+
user,
|
|
307
|
+
agent,
|
|
308
|
+
eoaAddress,
|
|
309
|
+
config.isTestnet,
|
|
310
|
+
tgClient,
|
|
311
|
+
authDataMap,
|
|
312
|
+
]);
|
|
213
313
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return;
|
|
314
|
+
const handleClearAgent = useCallback(() => {
|
|
315
|
+
if (eoaAddress) {
|
|
316
|
+
clearStoredAgent(eoaAddress);
|
|
317
|
+
setAgent(null);
|
|
219
318
|
}
|
|
319
|
+
}, [eoaAddress]);
|
|
220
320
|
|
|
221
|
-
|
|
222
|
-
|
|
321
|
+
// ── Wallet management (Telegram only) ───────────────────────
|
|
322
|
+
const createWallet = useCallback(
|
|
323
|
+
async (name: string): Promise<HyperliquidWallet> => {
|
|
324
|
+
const { response } = await tgClient.hyperliquidWalletCreate({
|
|
325
|
+
authData: authDataMap,
|
|
326
|
+
name,
|
|
327
|
+
});
|
|
328
|
+
refreshWallets();
|
|
329
|
+
if (!response.wallet)
|
|
330
|
+
throw new Error("Wallet creation returned no wallet");
|
|
331
|
+
return response.wallet;
|
|
332
|
+
},
|
|
333
|
+
[tgClient, authDataMap, refreshWallets],
|
|
334
|
+
);
|
|
223
335
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
console.error("[HypurrConnect] Failed to fetch USDC balance:", err);
|
|
234
|
-
if (!cancelled) setUsdcBalance(null);
|
|
235
|
-
} finally {
|
|
236
|
-
if (!cancelled) setUsdcBalanceLoading(false);
|
|
336
|
+
const deleteWallet = useCallback(
|
|
337
|
+
async (walletId: number): Promise<void> => {
|
|
338
|
+
await tgClient.hyperliquidWalletDelete({
|
|
339
|
+
authData: authDataMap,
|
|
340
|
+
walletId,
|
|
341
|
+
});
|
|
342
|
+
if (walletId === selectedWalletId) {
|
|
343
|
+
const remaining = wallets.filter((w) => w.id !== walletId);
|
|
344
|
+
setSelectedWalletId(remaining[0]?.id ?? 0);
|
|
237
345
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
};
|
|
243
|
-
}, [user?.address, infoClient, balanceTick]);
|
|
244
|
-
|
|
245
|
-
// ── Agent approval (EOA flow) ────────────────────────────────
|
|
246
|
-
const approveAgent = useCallback(
|
|
247
|
-
async (signTypedDataAsync: SignTypedDataFn) => {
|
|
248
|
-
if (!eoaAddress) throw new Error("No EOA address connected");
|
|
249
|
-
|
|
250
|
-
const { privateKey, address: agentAddress } = await generateAgentKey();
|
|
251
|
-
|
|
252
|
-
const isTestnet = config.isTestnet ?? false;
|
|
253
|
-
const nonce = Date.now();
|
|
254
|
-
const action = {
|
|
255
|
-
type: "approveAgent",
|
|
256
|
-
signatureChainId: isTestnet ? "0x66eee" : "0xa4b1",
|
|
257
|
-
hyperliquidChain: isTestnet ? "Testnet" : "Mainnet",
|
|
258
|
-
agentAddress: agentAddress.toLowerCase() as `0x${string}`,
|
|
259
|
-
agentName: null as string | null,
|
|
260
|
-
nonce,
|
|
261
|
-
};
|
|
262
|
-
const types = {
|
|
263
|
-
"HyperliquidTransaction:ApproveAgent": [
|
|
264
|
-
{ name: "hyperliquidChain", type: "string" },
|
|
265
|
-
{ name: "agentAddress", type: "address" },
|
|
266
|
-
{ name: "agentName", type: "string" },
|
|
267
|
-
{ name: "nonce", type: "uint64" },
|
|
268
|
-
],
|
|
269
|
-
};
|
|
346
|
+
refreshWallets();
|
|
347
|
+
},
|
|
348
|
+
[tgClient, authDataMap, selectedWalletId, wallets, refreshWallets],
|
|
349
|
+
);
|
|
270
350
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
verifyingContract: "0x0000000000000000000000000000000000000000",
|
|
277
|
-
},
|
|
278
|
-
types,
|
|
279
|
-
primaryType: "HyperliquidTransaction:ApproveAgent",
|
|
280
|
-
message: {
|
|
281
|
-
hyperliquidChain: action.hyperliquidChain,
|
|
282
|
-
agentAddress: action.agentAddress,
|
|
283
|
-
agentName: "",
|
|
284
|
-
nonce: BigInt(nonce),
|
|
285
|
-
},
|
|
351
|
+
const createWalletPack = useCallback(
|
|
352
|
+
async (name: string): Promise<number> => {
|
|
353
|
+
const { response } = await tgClient.telegramChatWalletPackCreate({
|
|
354
|
+
authData: authDataMap,
|
|
355
|
+
name,
|
|
286
356
|
});
|
|
357
|
+
refreshWallets();
|
|
358
|
+
return response.packId;
|
|
359
|
+
},
|
|
360
|
+
[tgClient, authDataMap, refreshWallets],
|
|
361
|
+
);
|
|
287
362
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
method: "POST",
|
|
298
|
-
headers: { "Content-Type": "application/json" },
|
|
299
|
-
body: JSON.stringify({
|
|
300
|
-
action,
|
|
301
|
-
nonce,
|
|
302
|
-
signature: { r, s, v },
|
|
303
|
-
}),
|
|
363
|
+
const addPackLabel = useCallback(
|
|
364
|
+
async (params: {
|
|
365
|
+
walletAddress: string;
|
|
366
|
+
walletLabel: string;
|
|
367
|
+
packId: number;
|
|
368
|
+
}): Promise<void> => {
|
|
369
|
+
await tgClient.telegramChatWalletPackLabelAdd({
|
|
370
|
+
authData: authDataMap,
|
|
371
|
+
...params,
|
|
304
372
|
});
|
|
373
|
+
refreshWallets();
|
|
374
|
+
},
|
|
375
|
+
[tgClient, authDataMap, refreshWallets],
|
|
376
|
+
);
|
|
305
377
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
setAgent(stored);
|
|
378
|
+
const modifyPackLabel = useCallback(
|
|
379
|
+
async (params: {
|
|
380
|
+
walletLabelOld: string;
|
|
381
|
+
walletLabelNew: string;
|
|
382
|
+
packId: number;
|
|
383
|
+
}): Promise<void> => {
|
|
384
|
+
await tgClient.telegramChatWalletPackLabelModify({
|
|
385
|
+
authData: authDataMap,
|
|
386
|
+
...params,
|
|
387
|
+
});
|
|
388
|
+
refreshWallets();
|
|
318
389
|
},
|
|
319
|
-
[
|
|
390
|
+
[tgClient, authDataMap, refreshWallets],
|
|
320
391
|
);
|
|
321
392
|
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
393
|
+
const removePackLabel = useCallback(
|
|
394
|
+
async (params: { walletLabel: string; packId: number }): Promise<void> => {
|
|
395
|
+
await tgClient.telegramChatWalletPackLabelRemove({
|
|
396
|
+
authData: authDataMap,
|
|
397
|
+
...params,
|
|
398
|
+
});
|
|
399
|
+
refreshWallets();
|
|
400
|
+
},
|
|
401
|
+
[tgClient, authDataMap, refreshWallets],
|
|
402
|
+
);
|
|
328
403
|
|
|
329
404
|
// ── Login modal state ────────────────────────────────────────
|
|
330
405
|
const [loginModalOpen, setLoginModalOpen] = useState(false);
|
|
@@ -337,50 +412,134 @@ export function HypurrConnectProvider({
|
|
|
337
412
|
localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(data));
|
|
338
413
|
setEoaAddress(null);
|
|
339
414
|
setAgent(null);
|
|
415
|
+
setEoaError(null);
|
|
340
416
|
}, []);
|
|
341
417
|
|
|
342
|
-
const
|
|
418
|
+
const connectEoa = useCallback((address: `0x${string}`) => {
|
|
343
419
|
setEoaAddress(address);
|
|
344
420
|
setTgLoginData(null);
|
|
345
421
|
setTgUser(null);
|
|
346
422
|
setTgError(null);
|
|
423
|
+
setEoaError(null);
|
|
347
424
|
localStorage.removeItem(TELEGRAM_STORAGE_KEY);
|
|
425
|
+
|
|
426
|
+
const existing = loadAgent(address);
|
|
427
|
+
if (existing && existing.validUntil > Date.now()) {
|
|
428
|
+
setAgent(existing);
|
|
429
|
+
} else {
|
|
430
|
+
if (existing) clearStoredAgent(address);
|
|
431
|
+
setAgent(null);
|
|
432
|
+
}
|
|
348
433
|
}, []);
|
|
349
434
|
|
|
435
|
+
const approveAgentFn = useCallback(
|
|
436
|
+
async (signTypedDataAsync: SignTypedDataFn, chainId: number) => {
|
|
437
|
+
if (!eoaAddress) {
|
|
438
|
+
throw new Error(
|
|
439
|
+
"[HypurrConnect] Cannot approve agent: no EOA wallet connected. Call connectEoa(address) first.",
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
setEoaLoading(true);
|
|
444
|
+
setEoaError(null);
|
|
445
|
+
try {
|
|
446
|
+
const existing = loadAgent(eoaAddress);
|
|
447
|
+
if (existing) {
|
|
448
|
+
const isTestnet = config.isTestnet ?? false;
|
|
449
|
+
const valid = await isAgentValid(existing, eoaAddress, isTestnet);
|
|
450
|
+
if (valid) {
|
|
451
|
+
setAgent(existing);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
clearStoredAgent(eoaAddress);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const { privateKey, address: agentAddress } = await generateAgentKey();
|
|
458
|
+
const isTestnet = config.isTestnet ?? false;
|
|
459
|
+
|
|
460
|
+
const wallet = {
|
|
461
|
+
signTypedData: signTypedDataAsync,
|
|
462
|
+
getAddresses: async () => [eoaAddress] as `0x${string}`[],
|
|
463
|
+
getChainId: async () => chainId,
|
|
464
|
+
};
|
|
465
|
+
const transport = new HttpTransport({ isTestnet });
|
|
466
|
+
|
|
467
|
+
await sdkApproveAgent(
|
|
468
|
+
{ transport, wallet },
|
|
469
|
+
{
|
|
470
|
+
agentAddress: agentAddress.toLowerCase() as `0x${string}`,
|
|
471
|
+
agentName: AGENT_NAME,
|
|
472
|
+
},
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
const remote = await fetchActiveAgent(eoaAddress, isTestnet);
|
|
476
|
+
const validUntil =
|
|
477
|
+
remote?.validUntil ?? Date.now() + 7 * 24 * 60 * 60 * 1000;
|
|
478
|
+
|
|
479
|
+
const stored: StoredAgent = {
|
|
480
|
+
privateKey,
|
|
481
|
+
address: agentAddress,
|
|
482
|
+
approvedAt: Date.now(),
|
|
483
|
+
validUntil,
|
|
484
|
+
};
|
|
485
|
+
saveAgent(eoaAddress, stored);
|
|
486
|
+
setAgent(stored);
|
|
487
|
+
} catch (err) {
|
|
488
|
+
console.error("[HypurrConnect] EOA agent approval failed:", err);
|
|
489
|
+
setEoaError(err instanceof Error ? err.message : String(err));
|
|
490
|
+
setAgent(null);
|
|
491
|
+
} finally {
|
|
492
|
+
setEoaLoading(false);
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
[eoaAddress, config.isTestnet],
|
|
496
|
+
);
|
|
497
|
+
|
|
350
498
|
const logout = useCallback(() => {
|
|
351
499
|
setTgLoginData(null);
|
|
352
500
|
setTgUser(null);
|
|
353
501
|
setTgError(null);
|
|
354
502
|
setEoaAddress(null);
|
|
355
503
|
setAgent(null);
|
|
504
|
+
setEoaError(null);
|
|
356
505
|
localStorage.removeItem(TELEGRAM_STORAGE_KEY);
|
|
357
506
|
}, []);
|
|
358
507
|
|
|
359
508
|
// ── Context value ────────────────────────────────────────────
|
|
360
|
-
const value = useMemo<
|
|
509
|
+
const value = useMemo<InternalConnectState>(
|
|
361
510
|
() => ({
|
|
362
511
|
user,
|
|
363
512
|
isLoggedIn: !!user,
|
|
364
|
-
isLoading: tgLoading,
|
|
365
|
-
error: tgError,
|
|
513
|
+
isLoading: tgLoading || eoaLoading,
|
|
514
|
+
error: tgError ?? eoaError,
|
|
366
515
|
authMethod,
|
|
367
516
|
exchange,
|
|
368
517
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
518
|
+
wallets,
|
|
519
|
+
selectedWalletId,
|
|
520
|
+
selectWallet,
|
|
521
|
+
|
|
522
|
+
createWallet,
|
|
523
|
+
deleteWallet,
|
|
524
|
+
refreshWallets,
|
|
525
|
+
|
|
526
|
+
packs,
|
|
527
|
+
createWalletPack,
|
|
528
|
+
addPackLabel,
|
|
529
|
+
modifyPackLabel,
|
|
530
|
+
removePackLabel,
|
|
372
531
|
|
|
373
532
|
loginModalOpen,
|
|
374
533
|
openLoginModal,
|
|
375
534
|
closeLoginModal,
|
|
376
535
|
|
|
377
536
|
loginTelegram,
|
|
378
|
-
|
|
537
|
+
connectEoa,
|
|
538
|
+
approveAgent: approveAgentFn,
|
|
379
539
|
logout,
|
|
380
540
|
|
|
381
541
|
agent,
|
|
382
|
-
agentReady
|
|
383
|
-
approveAgent,
|
|
542
|
+
agentReady,
|
|
384
543
|
clearAgent: handleClearAgent,
|
|
385
544
|
|
|
386
545
|
botId: config.telegram?.botId ?? "",
|
|
@@ -392,20 +551,31 @@ export function HypurrConnectProvider({
|
|
|
392
551
|
[
|
|
393
552
|
user,
|
|
394
553
|
tgLoading,
|
|
554
|
+
eoaLoading,
|
|
395
555
|
tgError,
|
|
556
|
+
eoaError,
|
|
396
557
|
authMethod,
|
|
397
558
|
exchange,
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
559
|
+
wallets,
|
|
560
|
+
selectedWalletId,
|
|
561
|
+
selectWallet,
|
|
562
|
+
createWallet,
|
|
563
|
+
deleteWallet,
|
|
564
|
+
refreshWallets,
|
|
565
|
+
packs,
|
|
566
|
+
createWalletPack,
|
|
567
|
+
addPackLabel,
|
|
568
|
+
modifyPackLabel,
|
|
569
|
+
removePackLabel,
|
|
401
570
|
loginModalOpen,
|
|
402
571
|
openLoginModal,
|
|
403
572
|
closeLoginModal,
|
|
404
573
|
loginTelegram,
|
|
405
|
-
|
|
574
|
+
connectEoa,
|
|
575
|
+
approveAgentFn,
|
|
406
576
|
logout,
|
|
407
577
|
agent,
|
|
408
|
-
|
|
578
|
+
agentReady,
|
|
409
579
|
handleClearAgent,
|
|
410
580
|
config.telegram?.botId,
|
|
411
581
|
authDataMap,
|
package/src/LoginModal.tsx
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type CSSProperties,
|
|
12
12
|
type ReactNode,
|
|
13
13
|
} from "react";
|
|
14
|
-
import {
|
|
14
|
+
import { useHypurrConnectInternal } from "./HypurrConnectProvider";
|
|
15
15
|
import { MetaMaskColorIcon } from "./icons/MetaMaskColorIcon";
|
|
16
16
|
import { TelegramColorIcon } from "./icons/TelegramColorIcon";
|
|
17
17
|
import type { TelegramLoginData } from "./types";
|
|
@@ -131,7 +131,7 @@ function HoverButton({
|
|
|
131
131
|
|
|
132
132
|
export function LoginModal({ onConnectWallet, walletIcon }: LoginModalProps) {
|
|
133
133
|
const { loginTelegram, loginModalOpen, closeLoginModal, botId } =
|
|
134
|
-
|
|
134
|
+
useHypurrConnectInternal();
|
|
135
135
|
|
|
136
136
|
const handleTelegramAuth = useCallback(
|
|
137
137
|
(user: TelegramLoginData) => {
|