@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
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// src/HypurrConnectProvider.tsx
|
|
2
2
|
import {
|
|
3
3
|
ExchangeClient,
|
|
4
|
-
HttpTransport
|
|
5
|
-
InfoClient
|
|
4
|
+
HttpTransport
|
|
6
5
|
} from "@hfunlabs/hyperliquid";
|
|
6
|
+
import { approveAgent as sdkApproveAgent } from "@hfunlabs/hyperliquid/api/exchange";
|
|
7
7
|
import { PrivateKeySigner } from "@hfunlabs/hyperliquid/signing";
|
|
8
8
|
import {
|
|
9
9
|
createContext,
|
|
@@ -11,10 +11,12 @@ import {
|
|
|
11
11
|
useContext,
|
|
12
12
|
useEffect,
|
|
13
13
|
useMemo,
|
|
14
|
+
useRef,
|
|
14
15
|
useState
|
|
15
16
|
} from "react";
|
|
16
17
|
|
|
17
18
|
// src/agent.ts
|
|
19
|
+
var AGENT_NAME = "hypurr-connect";
|
|
18
20
|
var AGENT_STORAGE_PREFIX = "hypurr-connect-agent";
|
|
19
21
|
function storageKey(masterAddress) {
|
|
20
22
|
return `${AGENT_STORAGE_PREFIX}:${masterAddress.toLowerCase()}`;
|
|
@@ -41,6 +43,39 @@ async function generateAgentKey() {
|
|
|
41
43
|
const signer = new PrivateKeySigner2(privateKey);
|
|
42
44
|
return { privateKey, address: signer.address };
|
|
43
45
|
}
|
|
46
|
+
async function fetchActiveAgent(userAddress, isTestnet) {
|
|
47
|
+
const url = isTestnet ? "https://api.hyperliquid-testnet.xyz/info" : "https://api.hyperliquid.xyz/info";
|
|
48
|
+
const res = await fetch(url, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: { "Content-Type": "application/json" },
|
|
51
|
+
body: JSON.stringify({ type: "extraAgents", user: userAddress })
|
|
52
|
+
});
|
|
53
|
+
if (!res.ok) return null;
|
|
54
|
+
const agents = await res.json();
|
|
55
|
+
if (!Array.isArray(agents)) return null;
|
|
56
|
+
const nowMs = Date.now();
|
|
57
|
+
const match = agents.find(
|
|
58
|
+
(a) => a.name === AGENT_NAME && a.validUntil * 1e3 > nowMs
|
|
59
|
+
);
|
|
60
|
+
if (!match) return null;
|
|
61
|
+
return { ...match, validUntil: match.validUntil * 1e3 };
|
|
62
|
+
}
|
|
63
|
+
async function isAgentValid(stored, userAddress, isTestnet) {
|
|
64
|
+
if (stored.validUntil <= Date.now()) return false;
|
|
65
|
+
const remote = await fetchActiveAgent(userAddress, isTestnet);
|
|
66
|
+
if (!remote) return false;
|
|
67
|
+
return remote.address.toLowerCase() === stored.address.toLowerCase() && remote.validUntil > Date.now();
|
|
68
|
+
}
|
|
69
|
+
var DEAD_AGENT_PATTERNS = [
|
|
70
|
+
/agent address .+ is not valid/i,
|
|
71
|
+
/unknown signer/i,
|
|
72
|
+
/not authorized/i,
|
|
73
|
+
/not an agent/i
|
|
74
|
+
];
|
|
75
|
+
function isDeadAgentError(err) {
|
|
76
|
+
const msg = err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : String(err);
|
|
77
|
+
return DEAD_AGENT_PATTERNS.some((p) => p.test(msg));
|
|
78
|
+
}
|
|
44
79
|
|
|
45
80
|
// src/grpc.ts
|
|
46
81
|
import { GrpcWebFetchTransport } from "@protobuf-ts/grpcweb-transport";
|
|
@@ -152,6 +187,14 @@ function useHypurrConnect() {
|
|
|
152
187
|
);
|
|
153
188
|
return ctx;
|
|
154
189
|
}
|
|
190
|
+
function useHypurrConnectInternal() {
|
|
191
|
+
const ctx = useContext(HypurrConnectContext);
|
|
192
|
+
if (!ctx)
|
|
193
|
+
throw new Error(
|
|
194
|
+
"useHypurrConnectInternal must be used within <HypurrConnectProvider>"
|
|
195
|
+
);
|
|
196
|
+
return ctx;
|
|
197
|
+
}
|
|
155
198
|
function HypurrConnectProvider({
|
|
156
199
|
config,
|
|
157
200
|
children
|
|
@@ -175,6 +218,7 @@ function HypurrConnectProvider({
|
|
|
175
218
|
() => tgLoginData ? toAuthDataMap(tgLoginData) : {},
|
|
176
219
|
[tgLoginData]
|
|
177
220
|
);
|
|
221
|
+
const [tgUserTick, setTgUserTick] = useState(0);
|
|
178
222
|
useEffect(() => {
|
|
179
223
|
if (!tgLoginData) return;
|
|
180
224
|
let cancelled = false;
|
|
@@ -183,9 +227,7 @@ function HypurrConnectProvider({
|
|
|
183
227
|
(async () => {
|
|
184
228
|
try {
|
|
185
229
|
const authData = toAuthDataMap(tgLoginData);
|
|
186
|
-
console.log(authData);
|
|
187
230
|
const { response } = await tgClient.telegramUser({ authData });
|
|
188
|
-
console.log(response);
|
|
189
231
|
if (cancelled) return;
|
|
190
232
|
setTgUser(response.user ?? null);
|
|
191
233
|
} catch (err) {
|
|
@@ -199,27 +241,55 @@ function HypurrConnectProvider({
|
|
|
199
241
|
return () => {
|
|
200
242
|
cancelled = true;
|
|
201
243
|
};
|
|
202
|
-
}, [tgLoginData, tgClient]);
|
|
244
|
+
}, [tgLoginData, tgClient, tgUserTick]);
|
|
203
245
|
const [eoaAddress, setEoaAddress] = useState(null);
|
|
204
246
|
const [agent, setAgent] = useState(null);
|
|
247
|
+
const [eoaLoading, setEoaLoading] = useState(false);
|
|
248
|
+
const [eoaError, setEoaError] = useState(null);
|
|
249
|
+
const authMethod = tgLoginData ? "telegram" : eoaAddress ? "eoa" : null;
|
|
250
|
+
const [wallets, setWallets] = useState([]);
|
|
251
|
+
const [selectedWalletId, setSelectedWalletId] = useState(0);
|
|
252
|
+
const [packs, setPacks] = useState([]);
|
|
253
|
+
const refreshWallets = useCallback(() => setTgUserTick((t) => t + 1), []);
|
|
205
254
|
useEffect(() => {
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
255
|
+
if (authMethod !== "telegram" || !tgUser) {
|
|
256
|
+
setWallets([]);
|
|
257
|
+
setSelectedWalletId(0);
|
|
258
|
+
setPacks([]);
|
|
259
|
+
return;
|
|
210
260
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
261
|
+
const userWallets = tgUser.wallets ?? [];
|
|
262
|
+
setWallets(userWallets);
|
|
263
|
+
setPacks(tgUser.packs ?? []);
|
|
264
|
+
const defaultId = tgUser.walletId || userWallets[0]?.id || 0;
|
|
265
|
+
setSelectedWalletId((prev) => {
|
|
266
|
+
if (prev && userWallets.some((w) => w.id === prev)) return prev;
|
|
267
|
+
return defaultId;
|
|
268
|
+
});
|
|
269
|
+
}, [authMethod, tgUser]);
|
|
270
|
+
const selectedWallet = useMemo(
|
|
271
|
+
() => wallets.find((w) => w.id === selectedWalletId) ?? wallets[0] ?? null,
|
|
272
|
+
[wallets, selectedWalletId]
|
|
273
|
+
);
|
|
274
|
+
const selectWallet = useCallback(
|
|
275
|
+
(walletId) => {
|
|
276
|
+
if (wallets.some((w) => w.id === walletId)) {
|
|
277
|
+
setSelectedWalletId(walletId);
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
[wallets]
|
|
281
|
+
);
|
|
214
282
|
const user = useMemo(() => {
|
|
215
|
-
if (tgLoginData && authMethod === "telegram") {
|
|
283
|
+
if (tgLoginData && authMethod === "telegram" && selectedWallet) {
|
|
216
284
|
return {
|
|
217
|
-
address:
|
|
218
|
-
walletId:
|
|
285
|
+
address: selectedWallet.ethereumAddress,
|
|
286
|
+
walletId: selectedWallet.id,
|
|
219
287
|
displayName: tgLoginData.username ? `@${tgLoginData.username}` : tgLoginData.first_name,
|
|
220
288
|
photoUrl: tgLoginData.photo_url,
|
|
221
289
|
authMethod: "telegram",
|
|
222
|
-
telegramId: String(tgLoginData.id)
|
|
290
|
+
telegramId: String(tgLoginData.id),
|
|
291
|
+
hfunScore: tgUser?.reputation?.hfunScore,
|
|
292
|
+
reputationScore: tgUser?.reputation?.reputationScore
|
|
223
293
|
};
|
|
224
294
|
}
|
|
225
295
|
if (eoaAddress && authMethod === "eoa") {
|
|
@@ -231,7 +301,16 @@ function HypurrConnectProvider({
|
|
|
231
301
|
};
|
|
232
302
|
}
|
|
233
303
|
return null;
|
|
234
|
-
}, [tgLoginData,
|
|
304
|
+
}, [tgLoginData, selectedWallet, eoaAddress, authMethod, tgUser]);
|
|
305
|
+
const onDeadAgentRef = useRef(
|
|
306
|
+
null
|
|
307
|
+
);
|
|
308
|
+
onDeadAgentRef.current = (addr) => {
|
|
309
|
+
clearAgent(addr);
|
|
310
|
+
setAgent(null);
|
|
311
|
+
setEoaError("Agent expired or was deregistered. Please reconnect.");
|
|
312
|
+
};
|
|
313
|
+
const agentReady = authMethod === "telegram" || authMethod === "eoa" && !!agent;
|
|
235
314
|
const exchange = useMemo(() => {
|
|
236
315
|
if (authMethod === "telegram" && user?.address) {
|
|
237
316
|
const transport = new GrpcExchangeTransport({
|
|
@@ -246,127 +325,129 @@ function HypurrConnectProvider({
|
|
|
246
325
|
userAddress: user.address
|
|
247
326
|
});
|
|
248
327
|
}
|
|
249
|
-
if (authMethod === "eoa" &&
|
|
328
|
+
if (authMethod === "eoa" && eoaAddress) {
|
|
329
|
+
if (!agent) {
|
|
330
|
+
const noAgentTransport = {
|
|
331
|
+
isTestnet: config.isTestnet ?? false,
|
|
332
|
+
request() {
|
|
333
|
+
throw new Error(
|
|
334
|
+
"[HypurrConnect] No agent key approved. Call approveAgent(signTypedDataAsync) before using the exchange client. This is required for EOA wallets to sign transactions on Hyperliquid."
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
return new ExchangeClient({
|
|
339
|
+
transport: noAgentTransport,
|
|
340
|
+
externalSigning: true,
|
|
341
|
+
userAddress: eoaAddress
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
const inner = new HttpTransport({
|
|
345
|
+
isTestnet: config.isTestnet ?? false
|
|
346
|
+
});
|
|
347
|
+
const deadAgentAddr = eoaAddress;
|
|
348
|
+
const guardedTransport = {
|
|
349
|
+
isTestnet: inner.isTestnet,
|
|
350
|
+
async request(endpoint, payload, signal) {
|
|
351
|
+
try {
|
|
352
|
+
return await inner.request(endpoint, payload, signal);
|
|
353
|
+
} catch (err) {
|
|
354
|
+
if (endpoint === "exchange" && isDeadAgentError(err)) {
|
|
355
|
+
onDeadAgentRef.current?.(deadAgentAddr);
|
|
356
|
+
}
|
|
357
|
+
throw err;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
};
|
|
250
361
|
const wallet = new PrivateKeySigner(agent.privateKey);
|
|
251
362
|
return new ExchangeClient({
|
|
252
|
-
transport:
|
|
253
|
-
isTestnet: config.isTestnet ?? false
|
|
254
|
-
}),
|
|
363
|
+
transport: guardedTransport,
|
|
255
364
|
wallet
|
|
256
365
|
});
|
|
257
366
|
}
|
|
258
367
|
return null;
|
|
259
|
-
}, [
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
);
|
|
268
|
-
const [usdcBalance, setUsdcBalance] = useState(null);
|
|
269
|
-
const [usdcBalanceLoading, setUsdcBalanceLoading] = useState(false);
|
|
270
|
-
const [balanceTick, setBalanceTick] = useState(0);
|
|
271
|
-
const refreshBalance = useCallback(() => setBalanceTick((t) => t + 1), []);
|
|
272
|
-
useEffect(() => {
|
|
273
|
-
const addr = user?.address;
|
|
274
|
-
if (!addr) {
|
|
275
|
-
setUsdcBalance(null);
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
let cancelled = false;
|
|
279
|
-
setUsdcBalanceLoading(true);
|
|
280
|
-
(async () => {
|
|
281
|
-
try {
|
|
282
|
-
const state = await infoClient.clearinghouseState({
|
|
283
|
-
user: addr
|
|
284
|
-
});
|
|
285
|
-
if (!cancelled) {
|
|
286
|
-
setUsdcBalance(state.withdrawable);
|
|
287
|
-
}
|
|
288
|
-
} catch (err) {
|
|
289
|
-
console.error("[HypurrConnect] Failed to fetch USDC balance:", err);
|
|
290
|
-
if (!cancelled) setUsdcBalance(null);
|
|
291
|
-
} finally {
|
|
292
|
-
if (!cancelled) setUsdcBalanceLoading(false);
|
|
293
|
-
}
|
|
294
|
-
})();
|
|
295
|
-
return () => {
|
|
296
|
-
cancelled = true;
|
|
297
|
-
};
|
|
298
|
-
}, [user?.address, infoClient, balanceTick]);
|
|
299
|
-
const approveAgent = useCallback(
|
|
300
|
-
async (signTypedDataAsync) => {
|
|
301
|
-
if (!eoaAddress) throw new Error("No EOA address connected");
|
|
302
|
-
const { privateKey, address: agentAddress } = await generateAgentKey();
|
|
303
|
-
const isTestnet = config.isTestnet ?? false;
|
|
304
|
-
const nonce = Date.now();
|
|
305
|
-
const action = {
|
|
306
|
-
type: "approveAgent",
|
|
307
|
-
signatureChainId: isTestnet ? "0x66eee" : "0xa4b1",
|
|
308
|
-
hyperliquidChain: isTestnet ? "Testnet" : "Mainnet",
|
|
309
|
-
agentAddress: agentAddress.toLowerCase(),
|
|
310
|
-
agentName: null,
|
|
311
|
-
nonce
|
|
312
|
-
};
|
|
313
|
-
const types = {
|
|
314
|
-
"HyperliquidTransaction:ApproveAgent": [
|
|
315
|
-
{ name: "hyperliquidChain", type: "string" },
|
|
316
|
-
{ name: "agentAddress", type: "address" },
|
|
317
|
-
{ name: "agentName", type: "string" },
|
|
318
|
-
{ name: "nonce", type: "uint64" }
|
|
319
|
-
]
|
|
320
|
-
};
|
|
321
|
-
const signature = await signTypedDataAsync({
|
|
322
|
-
domain: {
|
|
323
|
-
name: "HyperliquidSignTransaction",
|
|
324
|
-
version: "1",
|
|
325
|
-
chainId: isTestnet ? 421614 : 42161,
|
|
326
|
-
verifyingContract: "0x0000000000000000000000000000000000000000"
|
|
327
|
-
},
|
|
328
|
-
types,
|
|
329
|
-
primaryType: "HyperliquidTransaction:ApproveAgent",
|
|
330
|
-
message: {
|
|
331
|
-
hyperliquidChain: action.hyperliquidChain,
|
|
332
|
-
agentAddress: action.agentAddress,
|
|
333
|
-
agentName: "",
|
|
334
|
-
nonce: BigInt(nonce)
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
const r = `0x${signature.slice(2, 66)}`;
|
|
338
|
-
const s = `0x${signature.slice(66, 130)}`;
|
|
339
|
-
const v = parseInt(signature.slice(130, 132), 16);
|
|
340
|
-
const url = isTestnet ? "https://api.hyperliquid-testnet.xyz/exchange" : "https://api.hyperliquid.xyz/exchange";
|
|
341
|
-
const res = await fetch(url, {
|
|
342
|
-
method: "POST",
|
|
343
|
-
headers: { "Content-Type": "application/json" },
|
|
344
|
-
body: JSON.stringify({
|
|
345
|
-
action,
|
|
346
|
-
nonce,
|
|
347
|
-
signature: { r, s, v }
|
|
348
|
-
})
|
|
349
|
-
});
|
|
350
|
-
const body = await res.json();
|
|
351
|
-
if (body?.status !== "ok") {
|
|
352
|
-
throw new Error(`approveAgent failed: ${JSON.stringify(body)}`);
|
|
353
|
-
}
|
|
354
|
-
const stored = {
|
|
355
|
-
privateKey,
|
|
356
|
-
address: agentAddress,
|
|
357
|
-
approvedAt: Date.now()
|
|
358
|
-
};
|
|
359
|
-
saveAgent(eoaAddress, stored);
|
|
360
|
-
setAgent(stored);
|
|
361
|
-
},
|
|
362
|
-
[eoaAddress, config.isTestnet]
|
|
363
|
-
);
|
|
368
|
+
}, [
|
|
369
|
+
authMethod,
|
|
370
|
+
user,
|
|
371
|
+
agent,
|
|
372
|
+
eoaAddress,
|
|
373
|
+
config.isTestnet,
|
|
374
|
+
tgClient,
|
|
375
|
+
authDataMap
|
|
376
|
+
]);
|
|
364
377
|
const handleClearAgent = useCallback(() => {
|
|
365
378
|
if (eoaAddress) {
|
|
366
379
|
clearAgent(eoaAddress);
|
|
367
380
|
setAgent(null);
|
|
368
381
|
}
|
|
369
382
|
}, [eoaAddress]);
|
|
383
|
+
const createWallet = useCallback(
|
|
384
|
+
async (name) => {
|
|
385
|
+
const { response } = await tgClient.hyperliquidWalletCreate({
|
|
386
|
+
authData: authDataMap,
|
|
387
|
+
name
|
|
388
|
+
});
|
|
389
|
+
refreshWallets();
|
|
390
|
+
if (!response.wallet)
|
|
391
|
+
throw new Error("Wallet creation returned no wallet");
|
|
392
|
+
return response.wallet;
|
|
393
|
+
},
|
|
394
|
+
[tgClient, authDataMap, refreshWallets]
|
|
395
|
+
);
|
|
396
|
+
const deleteWallet = useCallback(
|
|
397
|
+
async (walletId) => {
|
|
398
|
+
await tgClient.hyperliquidWalletDelete({
|
|
399
|
+
authData: authDataMap,
|
|
400
|
+
walletId
|
|
401
|
+
});
|
|
402
|
+
if (walletId === selectedWalletId) {
|
|
403
|
+
const remaining = wallets.filter((w) => w.id !== walletId);
|
|
404
|
+
setSelectedWalletId(remaining[0]?.id ?? 0);
|
|
405
|
+
}
|
|
406
|
+
refreshWallets();
|
|
407
|
+
},
|
|
408
|
+
[tgClient, authDataMap, selectedWalletId, wallets, refreshWallets]
|
|
409
|
+
);
|
|
410
|
+
const createWalletPack = useCallback(
|
|
411
|
+
async (name) => {
|
|
412
|
+
const { response } = await tgClient.telegramChatWalletPackCreate({
|
|
413
|
+
authData: authDataMap,
|
|
414
|
+
name
|
|
415
|
+
});
|
|
416
|
+
refreshWallets();
|
|
417
|
+
return response.packId;
|
|
418
|
+
},
|
|
419
|
+
[tgClient, authDataMap, refreshWallets]
|
|
420
|
+
);
|
|
421
|
+
const addPackLabel = useCallback(
|
|
422
|
+
async (params) => {
|
|
423
|
+
await tgClient.telegramChatWalletPackLabelAdd({
|
|
424
|
+
authData: authDataMap,
|
|
425
|
+
...params
|
|
426
|
+
});
|
|
427
|
+
refreshWallets();
|
|
428
|
+
},
|
|
429
|
+
[tgClient, authDataMap, refreshWallets]
|
|
430
|
+
);
|
|
431
|
+
const modifyPackLabel = useCallback(
|
|
432
|
+
async (params) => {
|
|
433
|
+
await tgClient.telegramChatWalletPackLabelModify({
|
|
434
|
+
authData: authDataMap,
|
|
435
|
+
...params
|
|
436
|
+
});
|
|
437
|
+
refreshWallets();
|
|
438
|
+
},
|
|
439
|
+
[tgClient, authDataMap, refreshWallets]
|
|
440
|
+
);
|
|
441
|
+
const removePackLabel = useCallback(
|
|
442
|
+
async (params) => {
|
|
443
|
+
await tgClient.telegramChatWalletPackLabelRemove({
|
|
444
|
+
authData: authDataMap,
|
|
445
|
+
...params
|
|
446
|
+
});
|
|
447
|
+
refreshWallets();
|
|
448
|
+
},
|
|
449
|
+
[tgClient, authDataMap, refreshWallets]
|
|
450
|
+
);
|
|
370
451
|
const [loginModalOpen, setLoginModalOpen] = useState(false);
|
|
371
452
|
const openLoginModal = useCallback(() => setLoginModalOpen(true), []);
|
|
372
453
|
const closeLoginModal = useCallback(() => setLoginModalOpen(false), []);
|
|
@@ -375,42 +456,115 @@ function HypurrConnectProvider({
|
|
|
375
456
|
localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(data));
|
|
376
457
|
setEoaAddress(null);
|
|
377
458
|
setAgent(null);
|
|
459
|
+
setEoaError(null);
|
|
378
460
|
}, []);
|
|
379
|
-
const
|
|
461
|
+
const connectEoa = useCallback((address) => {
|
|
380
462
|
setEoaAddress(address);
|
|
381
463
|
setTgLoginData(null);
|
|
382
464
|
setTgUser(null);
|
|
383
465
|
setTgError(null);
|
|
466
|
+
setEoaError(null);
|
|
384
467
|
localStorage.removeItem(TELEGRAM_STORAGE_KEY);
|
|
468
|
+
const existing = loadAgent(address);
|
|
469
|
+
if (existing && existing.validUntil > Date.now()) {
|
|
470
|
+
setAgent(existing);
|
|
471
|
+
} else {
|
|
472
|
+
if (existing) clearAgent(address);
|
|
473
|
+
setAgent(null);
|
|
474
|
+
}
|
|
385
475
|
}, []);
|
|
476
|
+
const approveAgentFn = useCallback(
|
|
477
|
+
async (signTypedDataAsync, chainId) => {
|
|
478
|
+
if (!eoaAddress) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
"[HypurrConnect] Cannot approve agent: no EOA wallet connected. Call connectEoa(address) first."
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
setEoaLoading(true);
|
|
484
|
+
setEoaError(null);
|
|
485
|
+
try {
|
|
486
|
+
const existing = loadAgent(eoaAddress);
|
|
487
|
+
if (existing) {
|
|
488
|
+
const isTestnet2 = config.isTestnet ?? false;
|
|
489
|
+
const valid = await isAgentValid(existing, eoaAddress, isTestnet2);
|
|
490
|
+
if (valid) {
|
|
491
|
+
setAgent(existing);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
clearAgent(eoaAddress);
|
|
495
|
+
}
|
|
496
|
+
const { privateKey, address: agentAddress } = await generateAgentKey();
|
|
497
|
+
const isTestnet = config.isTestnet ?? false;
|
|
498
|
+
const wallet = {
|
|
499
|
+
signTypedData: signTypedDataAsync,
|
|
500
|
+
getAddresses: async () => [eoaAddress],
|
|
501
|
+
getChainId: async () => chainId
|
|
502
|
+
};
|
|
503
|
+
const transport = new HttpTransport({ isTestnet });
|
|
504
|
+
await sdkApproveAgent(
|
|
505
|
+
{ transport, wallet },
|
|
506
|
+
{
|
|
507
|
+
agentAddress: agentAddress.toLowerCase(),
|
|
508
|
+
agentName: AGENT_NAME
|
|
509
|
+
}
|
|
510
|
+
);
|
|
511
|
+
const remote = await fetchActiveAgent(eoaAddress, isTestnet);
|
|
512
|
+
const validUntil = remote?.validUntil ?? Date.now() + 7 * 24 * 60 * 60 * 1e3;
|
|
513
|
+
const stored = {
|
|
514
|
+
privateKey,
|
|
515
|
+
address: agentAddress,
|
|
516
|
+
approvedAt: Date.now(),
|
|
517
|
+
validUntil
|
|
518
|
+
};
|
|
519
|
+
saveAgent(eoaAddress, stored);
|
|
520
|
+
setAgent(stored);
|
|
521
|
+
} catch (err) {
|
|
522
|
+
console.error("[HypurrConnect] EOA agent approval failed:", err);
|
|
523
|
+
setEoaError(err instanceof Error ? err.message : String(err));
|
|
524
|
+
setAgent(null);
|
|
525
|
+
} finally {
|
|
526
|
+
setEoaLoading(false);
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
[eoaAddress, config.isTestnet]
|
|
530
|
+
);
|
|
386
531
|
const logout = useCallback(() => {
|
|
387
532
|
setTgLoginData(null);
|
|
388
533
|
setTgUser(null);
|
|
389
534
|
setTgError(null);
|
|
390
535
|
setEoaAddress(null);
|
|
391
536
|
setAgent(null);
|
|
537
|
+
setEoaError(null);
|
|
392
538
|
localStorage.removeItem(TELEGRAM_STORAGE_KEY);
|
|
393
539
|
}, []);
|
|
394
540
|
const value = useMemo(
|
|
395
541
|
() => ({
|
|
396
542
|
user,
|
|
397
543
|
isLoggedIn: !!user,
|
|
398
|
-
isLoading: tgLoading,
|
|
399
|
-
error: tgError,
|
|
544
|
+
isLoading: tgLoading || eoaLoading,
|
|
545
|
+
error: tgError ?? eoaError,
|
|
400
546
|
authMethod,
|
|
401
547
|
exchange,
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
548
|
+
wallets,
|
|
549
|
+
selectedWalletId,
|
|
550
|
+
selectWallet,
|
|
551
|
+
createWallet,
|
|
552
|
+
deleteWallet,
|
|
553
|
+
refreshWallets,
|
|
554
|
+
packs,
|
|
555
|
+
createWalletPack,
|
|
556
|
+
addPackLabel,
|
|
557
|
+
modifyPackLabel,
|
|
558
|
+
removePackLabel,
|
|
405
559
|
loginModalOpen,
|
|
406
560
|
openLoginModal,
|
|
407
561
|
closeLoginModal,
|
|
408
562
|
loginTelegram,
|
|
409
|
-
|
|
563
|
+
connectEoa,
|
|
564
|
+
approveAgent: approveAgentFn,
|
|
410
565
|
logout,
|
|
411
566
|
agent,
|
|
412
|
-
agentReady
|
|
413
|
-
approveAgent,
|
|
567
|
+
agentReady,
|
|
414
568
|
clearAgent: handleClearAgent,
|
|
415
569
|
botId: config.telegram?.botId ?? "",
|
|
416
570
|
authDataMap,
|
|
@@ -420,20 +574,31 @@ function HypurrConnectProvider({
|
|
|
420
574
|
[
|
|
421
575
|
user,
|
|
422
576
|
tgLoading,
|
|
577
|
+
eoaLoading,
|
|
423
578
|
tgError,
|
|
579
|
+
eoaError,
|
|
424
580
|
authMethod,
|
|
425
581
|
exchange,
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
582
|
+
wallets,
|
|
583
|
+
selectedWalletId,
|
|
584
|
+
selectWallet,
|
|
585
|
+
createWallet,
|
|
586
|
+
deleteWallet,
|
|
587
|
+
refreshWallets,
|
|
588
|
+
packs,
|
|
589
|
+
createWalletPack,
|
|
590
|
+
addPackLabel,
|
|
591
|
+
modifyPackLabel,
|
|
592
|
+
removePackLabel,
|
|
429
593
|
loginModalOpen,
|
|
430
594
|
openLoginModal,
|
|
431
595
|
closeLoginModal,
|
|
432
596
|
loginTelegram,
|
|
433
|
-
|
|
597
|
+
connectEoa,
|
|
598
|
+
approveAgentFn,
|
|
434
599
|
logout,
|
|
435
600
|
agent,
|
|
436
|
-
|
|
601
|
+
agentReady,
|
|
437
602
|
handleClearAgent,
|
|
438
603
|
config.telegram?.botId,
|
|
439
604
|
authDataMap,
|
|
@@ -674,7 +839,7 @@ function HoverButton({
|
|
|
674
839
|
);
|
|
675
840
|
}
|
|
676
841
|
function LoginModal({ onConnectWallet, walletIcon }) {
|
|
677
|
-
const { loginTelegram, loginModalOpen, closeLoginModal, botId } =
|
|
842
|
+
const { loginTelegram, loginModalOpen, closeLoginModal, botId } = useHypurrConnectInternal();
|
|
678
843
|
const handleTelegramAuth = useCallback2(
|
|
679
844
|
(user) => {
|
|
680
845
|
loginTelegram(user);
|