@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
|
@@ -1,19 +1,33 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ExchangeClient,
|
|
3
|
+
HttpTransport,
|
|
4
|
+
type IRequestTransport,
|
|
5
|
+
} from "@hfunlabs/hyperliquid";
|
|
6
|
+
import { approveAgent as sdkApproveAgent } from "@hfunlabs/hyperliquid/api/exchange";
|
|
2
7
|
import { PrivateKeySigner } from "@hfunlabs/hyperliquid/signing";
|
|
3
8
|
import type { TelegramUserResponse } from "hypurr-grpc/ts/hypurr/telegram/telegram_service";
|
|
4
|
-
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";
|
|
5
14
|
import {
|
|
6
15
|
createContext,
|
|
7
16
|
useCallback,
|
|
8
17
|
useContext,
|
|
9
18
|
useEffect,
|
|
10
19
|
useMemo,
|
|
20
|
+
useRef,
|
|
11
21
|
useState,
|
|
12
22
|
type ReactNode,
|
|
13
23
|
} from "react";
|
|
14
24
|
import {
|
|
25
|
+
AGENT_NAME,
|
|
15
26
|
clearAgent as clearStoredAgent,
|
|
27
|
+
fetchActiveAgent,
|
|
16
28
|
generateAgentKey,
|
|
29
|
+
isAgentValid,
|
|
30
|
+
isDeadAgentError,
|
|
17
31
|
loadAgent,
|
|
18
32
|
saveAgent,
|
|
19
33
|
} from "./agent";
|
|
@@ -29,6 +43,11 @@ import type {
|
|
|
29
43
|
TelegramLoginData,
|
|
30
44
|
} from "./types";
|
|
31
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
|
+
|
|
32
51
|
const TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-user";
|
|
33
52
|
|
|
34
53
|
function toAuthDataMap(data: TelegramLoginData): Record<string, string> {
|
|
@@ -44,7 +63,7 @@ function toAuthDataMap(data: TelegramLoginData): Record<string, string> {
|
|
|
44
63
|
return map;
|
|
45
64
|
}
|
|
46
65
|
|
|
47
|
-
const HypurrConnectContext = createContext<
|
|
66
|
+
const HypurrConnectContext = createContext<InternalConnectState | null>(null);
|
|
48
67
|
|
|
49
68
|
export function useHypurrConnect(): HypurrConnectState {
|
|
50
69
|
const ctx = useContext(HypurrConnectContext);
|
|
@@ -55,6 +74,16 @@ export function useHypurrConnect(): HypurrConnectState {
|
|
|
55
74
|
return ctx;
|
|
56
75
|
}
|
|
57
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
|
+
|
|
58
87
|
export function HypurrConnectProvider({
|
|
59
88
|
config,
|
|
60
89
|
children,
|
|
@@ -85,6 +114,8 @@ export function HypurrConnectProvider({
|
|
|
85
114
|
[tgLoginData],
|
|
86
115
|
);
|
|
87
116
|
|
|
117
|
+
const [tgUserTick, setTgUserTick] = useState(0);
|
|
118
|
+
|
|
88
119
|
useEffect(() => {
|
|
89
120
|
if (!tgLoginData) return;
|
|
90
121
|
let cancelled = false;
|
|
@@ -94,9 +125,7 @@ export function HypurrConnectProvider({
|
|
|
94
125
|
(async () => {
|
|
95
126
|
try {
|
|
96
127
|
const authData = toAuthDataMap(tgLoginData);
|
|
97
|
-
console.log(authData);
|
|
98
128
|
const { response } = await tgClient.telegramUser({ authData });
|
|
99
|
-
console.log(response);
|
|
100
129
|
if (cancelled) return;
|
|
101
130
|
setTgUser((response as TelegramUserResponse).user ?? null);
|
|
102
131
|
} catch (err) {
|
|
@@ -111,19 +140,13 @@ export function HypurrConnectProvider({
|
|
|
111
140
|
return () => {
|
|
112
141
|
cancelled = true;
|
|
113
142
|
};
|
|
114
|
-
}, [tgLoginData, tgClient]);
|
|
143
|
+
}, [tgLoginData, tgClient, tgUserTick]);
|
|
115
144
|
|
|
116
145
|
// ── EOA auth state ───────────────────────────────────────────
|
|
117
146
|
const [eoaAddress, setEoaAddress] = useState<`0x${string}` | null>(null);
|
|
118
147
|
const [agent, setAgent] = useState<StoredAgent | null>(null);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (eoaAddress) {
|
|
122
|
-
setAgent(loadAgent(eoaAddress));
|
|
123
|
-
} else {
|
|
124
|
-
setAgent(null);
|
|
125
|
-
}
|
|
126
|
-
}, [eoaAddress]);
|
|
148
|
+
const [eoaLoading, setEoaLoading] = useState(false);
|
|
149
|
+
const [eoaError, setEoaError] = useState<string | null>(null);
|
|
127
150
|
|
|
128
151
|
// ── Derived auth ─────────────────────────────────────────────
|
|
129
152
|
const authMethod: AuthMethod = tgLoginData
|
|
@@ -132,19 +155,59 @@ export function HypurrConnectProvider({
|
|
|
132
155
|
? "eoa"
|
|
133
156
|
: null;
|
|
134
157
|
|
|
135
|
-
|
|
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
|
+
);
|
|
136
197
|
|
|
137
198
|
const user = useMemo<HypurrUser | null>(() => {
|
|
138
|
-
if (tgLoginData && authMethod === "telegram") {
|
|
199
|
+
if (tgLoginData && authMethod === "telegram" && selectedWallet) {
|
|
139
200
|
return {
|
|
140
|
-
address:
|
|
141
|
-
walletId:
|
|
201
|
+
address: selectedWallet.ethereumAddress,
|
|
202
|
+
walletId: selectedWallet.id,
|
|
142
203
|
displayName: tgLoginData.username
|
|
143
204
|
? `@${tgLoginData.username}`
|
|
144
205
|
: tgLoginData.first_name,
|
|
145
206
|
photoUrl: tgLoginData.photo_url,
|
|
146
207
|
authMethod: "telegram",
|
|
147
208
|
telegramId: String(tgLoginData.id),
|
|
209
|
+
hfunScore: tgUser?.reputation?.hfunScore,
|
|
210
|
+
reputationScore: tgUser?.reputation?.reputationScore,
|
|
148
211
|
};
|
|
149
212
|
}
|
|
150
213
|
if (eoaAddress && authMethod === "eoa") {
|
|
@@ -156,11 +219,24 @@ export function HypurrConnectProvider({
|
|
|
156
219
|
};
|
|
157
220
|
}
|
|
158
221
|
return null;
|
|
159
|
-
}, [tgLoginData,
|
|
222
|
+
}, [tgLoginData, selectedWallet, eoaAddress, authMethod, tgUser]);
|
|
160
223
|
|
|
161
224
|
// ── Exchange client ──────────────────────────────────────────
|
|
162
225
|
// Telegram: GrpcExchangeTransport → HyperliquidCoreAction (server signs)
|
|
163
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
|
+
|
|
164
240
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
165
241
|
const exchange = useMemo<ExchangeClient<any> | null>(() => {
|
|
166
242
|
if (authMethod === "telegram" && user?.address) {
|
|
@@ -177,150 +253,153 @@ export function HypurrConnectProvider({
|
|
|
177
253
|
});
|
|
178
254
|
}
|
|
179
255
|
|
|
180
|
-
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
|
+
};
|
|
181
296
|
const wallet = new PrivateKeySigner(agent.privateKey);
|
|
182
297
|
return new ExchangeClient({
|
|
183
|
-
transport:
|
|
184
|
-
isTestnet: config.isTestnet ?? false,
|
|
185
|
-
}),
|
|
298
|
+
transport: guardedTransport,
|
|
186
299
|
wallet,
|
|
187
300
|
});
|
|
188
301
|
}
|
|
189
302
|
|
|
190
303
|
return null;
|
|
191
|
-
}, [
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}),
|
|
201
|
-
[config.isTestnet],
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
const [usdcBalance, setUsdcBalance] = useState<string | null>(null);
|
|
205
|
-
const [usdcBalanceLoading, setUsdcBalanceLoading] = useState(false);
|
|
206
|
-
const [balanceTick, setBalanceTick] = useState(0);
|
|
207
|
-
|
|
208
|
-
const refreshBalance = useCallback(() => setBalanceTick((t) => t + 1), []);
|
|
304
|
+
}, [
|
|
305
|
+
authMethod,
|
|
306
|
+
user,
|
|
307
|
+
agent,
|
|
308
|
+
eoaAddress,
|
|
309
|
+
config.isTestnet,
|
|
310
|
+
tgClient,
|
|
311
|
+
authDataMap,
|
|
312
|
+
]);
|
|
209
313
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
return;
|
|
314
|
+
const handleClearAgent = useCallback(() => {
|
|
315
|
+
if (eoaAddress) {
|
|
316
|
+
clearStoredAgent(eoaAddress);
|
|
317
|
+
setAgent(null);
|
|
215
318
|
}
|
|
319
|
+
}, [eoaAddress]);
|
|
216
320
|
|
|
217
|
-
|
|
218
|
-
|
|
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
|
+
);
|
|
219
335
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
console.error("[HypurrConnect] Failed to fetch USDC balance:", err);
|
|
230
|
-
if (!cancelled) setUsdcBalance(null);
|
|
231
|
-
} finally {
|
|
232
|
-
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);
|
|
233
345
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
};
|
|
239
|
-
}, [user?.address, infoClient, balanceTick]);
|
|
240
|
-
|
|
241
|
-
// ── Agent approval (EOA flow) ────────────────────────────────
|
|
242
|
-
const approveAgent = useCallback(
|
|
243
|
-
async (signTypedDataAsync: SignTypedDataFn) => {
|
|
244
|
-
if (!eoaAddress) throw new Error("No EOA address connected");
|
|
245
|
-
|
|
246
|
-
const { privateKey, address: agentAddress } = await generateAgentKey();
|
|
247
|
-
|
|
248
|
-
const isTestnet = config.isTestnet ?? false;
|
|
249
|
-
const nonce = Date.now();
|
|
250
|
-
const action = {
|
|
251
|
-
type: "approveAgent",
|
|
252
|
-
signatureChainId: isTestnet ? "0x66eee" : "0xa4b1",
|
|
253
|
-
hyperliquidChain: isTestnet ? "Testnet" : "Mainnet",
|
|
254
|
-
agentAddress: agentAddress.toLowerCase() as `0x${string}`,
|
|
255
|
-
agentName: null as string | null,
|
|
256
|
-
nonce,
|
|
257
|
-
};
|
|
258
|
-
const types = {
|
|
259
|
-
"HyperliquidTransaction:ApproveAgent": [
|
|
260
|
-
{ name: "hyperliquidChain", type: "string" },
|
|
261
|
-
{ name: "agentAddress", type: "address" },
|
|
262
|
-
{ name: "agentName", type: "string" },
|
|
263
|
-
{ name: "nonce", type: "uint64" },
|
|
264
|
-
],
|
|
265
|
-
};
|
|
346
|
+
refreshWallets();
|
|
347
|
+
},
|
|
348
|
+
[tgClient, authDataMap, selectedWalletId, wallets, refreshWallets],
|
|
349
|
+
);
|
|
266
350
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
verifyingContract: "0x0000000000000000000000000000000000000000",
|
|
273
|
-
},
|
|
274
|
-
types,
|
|
275
|
-
primaryType: "HyperliquidTransaction:ApproveAgent",
|
|
276
|
-
message: {
|
|
277
|
-
hyperliquidChain: action.hyperliquidChain,
|
|
278
|
-
agentAddress: action.agentAddress,
|
|
279
|
-
agentName: "",
|
|
280
|
-
nonce: BigInt(nonce),
|
|
281
|
-
},
|
|
351
|
+
const createWalletPack = useCallback(
|
|
352
|
+
async (name: string): Promise<number> => {
|
|
353
|
+
const { response } = await tgClient.telegramChatWalletPackCreate({
|
|
354
|
+
authData: authDataMap,
|
|
355
|
+
name,
|
|
282
356
|
});
|
|
357
|
+
refreshWallets();
|
|
358
|
+
return response.packId;
|
|
359
|
+
},
|
|
360
|
+
[tgClient, authDataMap, refreshWallets],
|
|
361
|
+
);
|
|
283
362
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
method: "POST",
|
|
294
|
-
headers: { "Content-Type": "application/json" },
|
|
295
|
-
body: JSON.stringify({
|
|
296
|
-
action,
|
|
297
|
-
nonce,
|
|
298
|
-
signature: { r, s, v },
|
|
299
|
-
}),
|
|
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,
|
|
300
372
|
});
|
|
373
|
+
refreshWallets();
|
|
374
|
+
},
|
|
375
|
+
[tgClient, authDataMap, refreshWallets],
|
|
376
|
+
);
|
|
301
377
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
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();
|
|
314
389
|
},
|
|
315
|
-
[
|
|
390
|
+
[tgClient, authDataMap, refreshWallets],
|
|
316
391
|
);
|
|
317
392
|
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
+
);
|
|
324
403
|
|
|
325
404
|
// ── Login modal state ────────────────────────────────────────
|
|
326
405
|
const [loginModalOpen, setLoginModalOpen] = useState(false);
|
|
@@ -333,50 +412,134 @@ export function HypurrConnectProvider({
|
|
|
333
412
|
localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(data));
|
|
334
413
|
setEoaAddress(null);
|
|
335
414
|
setAgent(null);
|
|
415
|
+
setEoaError(null);
|
|
336
416
|
}, []);
|
|
337
417
|
|
|
338
|
-
const
|
|
418
|
+
const connectEoa = useCallback((address: `0x${string}`) => {
|
|
339
419
|
setEoaAddress(address);
|
|
340
420
|
setTgLoginData(null);
|
|
341
421
|
setTgUser(null);
|
|
342
422
|
setTgError(null);
|
|
423
|
+
setEoaError(null);
|
|
343
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
|
+
}
|
|
344
433
|
}, []);
|
|
345
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
|
+
|
|
346
498
|
const logout = useCallback(() => {
|
|
347
499
|
setTgLoginData(null);
|
|
348
500
|
setTgUser(null);
|
|
349
501
|
setTgError(null);
|
|
350
502
|
setEoaAddress(null);
|
|
351
503
|
setAgent(null);
|
|
504
|
+
setEoaError(null);
|
|
352
505
|
localStorage.removeItem(TELEGRAM_STORAGE_KEY);
|
|
353
506
|
}, []);
|
|
354
507
|
|
|
355
508
|
// ── Context value ────────────────────────────────────────────
|
|
356
|
-
const value = useMemo<
|
|
509
|
+
const value = useMemo<InternalConnectState>(
|
|
357
510
|
() => ({
|
|
358
511
|
user,
|
|
359
512
|
isLoggedIn: !!user,
|
|
360
|
-
isLoading: tgLoading,
|
|
361
|
-
error: tgError,
|
|
513
|
+
isLoading: tgLoading || eoaLoading,
|
|
514
|
+
error: tgError ?? eoaError,
|
|
362
515
|
authMethod,
|
|
363
516
|
exchange,
|
|
364
517
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
518
|
+
wallets,
|
|
519
|
+
selectedWalletId,
|
|
520
|
+
selectWallet,
|
|
521
|
+
|
|
522
|
+
createWallet,
|
|
523
|
+
deleteWallet,
|
|
524
|
+
refreshWallets,
|
|
525
|
+
|
|
526
|
+
packs,
|
|
527
|
+
createWalletPack,
|
|
528
|
+
addPackLabel,
|
|
529
|
+
modifyPackLabel,
|
|
530
|
+
removePackLabel,
|
|
368
531
|
|
|
369
532
|
loginModalOpen,
|
|
370
533
|
openLoginModal,
|
|
371
534
|
closeLoginModal,
|
|
372
535
|
|
|
373
536
|
loginTelegram,
|
|
374
|
-
|
|
537
|
+
connectEoa,
|
|
538
|
+
approveAgent: approveAgentFn,
|
|
375
539
|
logout,
|
|
376
540
|
|
|
377
541
|
agent,
|
|
378
|
-
agentReady
|
|
379
|
-
approveAgent,
|
|
542
|
+
agentReady,
|
|
380
543
|
clearAgent: handleClearAgent,
|
|
381
544
|
|
|
382
545
|
botId: config.telegram?.botId ?? "",
|
|
@@ -388,20 +551,31 @@ export function HypurrConnectProvider({
|
|
|
388
551
|
[
|
|
389
552
|
user,
|
|
390
553
|
tgLoading,
|
|
554
|
+
eoaLoading,
|
|
391
555
|
tgError,
|
|
556
|
+
eoaError,
|
|
392
557
|
authMethod,
|
|
393
558
|
exchange,
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
559
|
+
wallets,
|
|
560
|
+
selectedWalletId,
|
|
561
|
+
selectWallet,
|
|
562
|
+
createWallet,
|
|
563
|
+
deleteWallet,
|
|
564
|
+
refreshWallets,
|
|
565
|
+
packs,
|
|
566
|
+
createWalletPack,
|
|
567
|
+
addPackLabel,
|
|
568
|
+
modifyPackLabel,
|
|
569
|
+
removePackLabel,
|
|
397
570
|
loginModalOpen,
|
|
398
571
|
openLoginModal,
|
|
399
572
|
closeLoginModal,
|
|
400
573
|
loginTelegram,
|
|
401
|
-
|
|
574
|
+
connectEoa,
|
|
575
|
+
approveAgentFn,
|
|
402
576
|
logout,
|
|
403
577
|
agent,
|
|
404
|
-
|
|
578
|
+
agentReady,
|
|
405
579
|
handleClearAgent,
|
|
406
580
|
config.telegram?.botId,
|
|
407
581
|
authDataMap,
|