@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
package/dist/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// src/HypurrConnectProvider.tsx
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ExchangeClient,
|
|
4
|
+
HttpTransport
|
|
5
|
+
} from "@hfunlabs/hyperliquid";
|
|
6
|
+
import { approveAgent as sdkApproveAgent } from "@hfunlabs/hyperliquid/api/exchange";
|
|
3
7
|
import { PrivateKeySigner } from "@hfunlabs/hyperliquid/signing";
|
|
4
8
|
import {
|
|
5
9
|
createContext,
|
|
@@ -7,10 +11,12 @@ import {
|
|
|
7
11
|
useContext,
|
|
8
12
|
useEffect,
|
|
9
13
|
useMemo,
|
|
14
|
+
useRef,
|
|
10
15
|
useState
|
|
11
16
|
} from "react";
|
|
12
17
|
|
|
13
18
|
// src/agent.ts
|
|
19
|
+
var AGENT_NAME = "hypurr-connect";
|
|
14
20
|
var AGENT_STORAGE_PREFIX = "hypurr-connect-agent";
|
|
15
21
|
function storageKey(masterAddress) {
|
|
16
22
|
return `${AGENT_STORAGE_PREFIX}:${masterAddress.toLowerCase()}`;
|
|
@@ -37,6 +43,39 @@ async function generateAgentKey() {
|
|
|
37
43
|
const signer = new PrivateKeySigner2(privateKey);
|
|
38
44
|
return { privateKey, address: signer.address };
|
|
39
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
|
+
}
|
|
40
79
|
|
|
41
80
|
// src/grpc.ts
|
|
42
81
|
import { GrpcWebFetchTransport } from "@protobuf-ts/grpcweb-transport";
|
|
@@ -148,6 +187,14 @@ function useHypurrConnect() {
|
|
|
148
187
|
);
|
|
149
188
|
return ctx;
|
|
150
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
|
+
}
|
|
151
198
|
function HypurrConnectProvider({
|
|
152
199
|
config,
|
|
153
200
|
children
|
|
@@ -171,6 +218,7 @@ function HypurrConnectProvider({
|
|
|
171
218
|
() => tgLoginData ? toAuthDataMap(tgLoginData) : {},
|
|
172
219
|
[tgLoginData]
|
|
173
220
|
);
|
|
221
|
+
const [tgUserTick, setTgUserTick] = useState(0);
|
|
174
222
|
useEffect(() => {
|
|
175
223
|
if (!tgLoginData) return;
|
|
176
224
|
let cancelled = false;
|
|
@@ -179,9 +227,7 @@ function HypurrConnectProvider({
|
|
|
179
227
|
(async () => {
|
|
180
228
|
try {
|
|
181
229
|
const authData = toAuthDataMap(tgLoginData);
|
|
182
|
-
console.log(authData);
|
|
183
230
|
const { response } = await tgClient.telegramUser({ authData });
|
|
184
|
-
console.log(response);
|
|
185
231
|
if (cancelled) return;
|
|
186
232
|
setTgUser(response.user ?? null);
|
|
187
233
|
} catch (err) {
|
|
@@ -195,27 +241,55 @@ function HypurrConnectProvider({
|
|
|
195
241
|
return () => {
|
|
196
242
|
cancelled = true;
|
|
197
243
|
};
|
|
198
|
-
}, [tgLoginData, tgClient]);
|
|
244
|
+
}, [tgLoginData, tgClient, tgUserTick]);
|
|
199
245
|
const [eoaAddress, setEoaAddress] = useState(null);
|
|
200
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), []);
|
|
201
254
|
useEffect(() => {
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
255
|
+
if (authMethod !== "telegram" || !tgUser) {
|
|
256
|
+
setWallets([]);
|
|
257
|
+
setSelectedWalletId(0);
|
|
258
|
+
setPacks([]);
|
|
259
|
+
return;
|
|
206
260
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
+
);
|
|
210
282
|
const user = useMemo(() => {
|
|
211
|
-
if (tgLoginData && authMethod === "telegram") {
|
|
283
|
+
if (tgLoginData && authMethod === "telegram" && selectedWallet) {
|
|
212
284
|
return {
|
|
213
|
-
address:
|
|
214
|
-
walletId:
|
|
285
|
+
address: selectedWallet.ethereumAddress,
|
|
286
|
+
walletId: selectedWallet.id,
|
|
215
287
|
displayName: tgLoginData.username ? `@${tgLoginData.username}` : tgLoginData.first_name,
|
|
216
288
|
photoUrl: tgLoginData.photo_url,
|
|
217
289
|
authMethod: "telegram",
|
|
218
|
-
telegramId: String(tgLoginData.id)
|
|
290
|
+
telegramId: String(tgLoginData.id),
|
|
291
|
+
hfunScore: tgUser?.reputation?.hfunScore,
|
|
292
|
+
reputationScore: tgUser?.reputation?.reputationScore
|
|
219
293
|
};
|
|
220
294
|
}
|
|
221
295
|
if (eoaAddress && authMethod === "eoa") {
|
|
@@ -227,7 +301,16 @@ function HypurrConnectProvider({
|
|
|
227
301
|
};
|
|
228
302
|
}
|
|
229
303
|
return null;
|
|
230
|
-
}, [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;
|
|
231
314
|
const exchange = useMemo(() => {
|
|
232
315
|
if (authMethod === "telegram" && user?.address) {
|
|
233
316
|
const transport = new GrpcExchangeTransport({
|
|
@@ -242,127 +325,129 @@ function HypurrConnectProvider({
|
|
|
242
325
|
userAddress: user.address
|
|
243
326
|
});
|
|
244
327
|
}
|
|
245
|
-
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
|
+
};
|
|
246
361
|
const wallet = new PrivateKeySigner(agent.privateKey);
|
|
247
362
|
return new ExchangeClient({
|
|
248
|
-
transport:
|
|
249
|
-
isTestnet: config.isTestnet ?? false
|
|
250
|
-
}),
|
|
363
|
+
transport: guardedTransport,
|
|
251
364
|
wallet
|
|
252
365
|
});
|
|
253
366
|
}
|
|
254
367
|
return null;
|
|
255
|
-
}, [
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
);
|
|
264
|
-
const [usdcBalance, setUsdcBalance] = useState(null);
|
|
265
|
-
const [usdcBalanceLoading, setUsdcBalanceLoading] = useState(false);
|
|
266
|
-
const [balanceTick, setBalanceTick] = useState(0);
|
|
267
|
-
const refreshBalance = useCallback(() => setBalanceTick((t) => t + 1), []);
|
|
268
|
-
useEffect(() => {
|
|
269
|
-
const addr = user?.address;
|
|
270
|
-
if (!addr) {
|
|
271
|
-
setUsdcBalance(null);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
let cancelled = false;
|
|
275
|
-
setUsdcBalanceLoading(true);
|
|
276
|
-
(async () => {
|
|
277
|
-
try {
|
|
278
|
-
const state = await infoClient.clearinghouseState({
|
|
279
|
-
user: addr
|
|
280
|
-
});
|
|
281
|
-
if (!cancelled) {
|
|
282
|
-
setUsdcBalance(state.withdrawable);
|
|
283
|
-
}
|
|
284
|
-
} catch (err) {
|
|
285
|
-
console.error("[HypurrConnect] Failed to fetch USDC balance:", err);
|
|
286
|
-
if (!cancelled) setUsdcBalance(null);
|
|
287
|
-
} finally {
|
|
288
|
-
if (!cancelled) setUsdcBalanceLoading(false);
|
|
289
|
-
}
|
|
290
|
-
})();
|
|
291
|
-
return () => {
|
|
292
|
-
cancelled = true;
|
|
293
|
-
};
|
|
294
|
-
}, [user?.address, infoClient, balanceTick]);
|
|
295
|
-
const approveAgent = useCallback(
|
|
296
|
-
async (signTypedDataAsync) => {
|
|
297
|
-
if (!eoaAddress) throw new Error("No EOA address connected");
|
|
298
|
-
const { privateKey, address: agentAddress } = await generateAgentKey();
|
|
299
|
-
const isTestnet = config.isTestnet ?? false;
|
|
300
|
-
const nonce = Date.now();
|
|
301
|
-
const action = {
|
|
302
|
-
type: "approveAgent",
|
|
303
|
-
signatureChainId: isTestnet ? "0x66eee" : "0xa4b1",
|
|
304
|
-
hyperliquidChain: isTestnet ? "Testnet" : "Mainnet",
|
|
305
|
-
agentAddress: agentAddress.toLowerCase(),
|
|
306
|
-
agentName: null,
|
|
307
|
-
nonce
|
|
308
|
-
};
|
|
309
|
-
const types = {
|
|
310
|
-
"HyperliquidTransaction:ApproveAgent": [
|
|
311
|
-
{ name: "hyperliquidChain", type: "string" },
|
|
312
|
-
{ name: "agentAddress", type: "address" },
|
|
313
|
-
{ name: "agentName", type: "string" },
|
|
314
|
-
{ name: "nonce", type: "uint64" }
|
|
315
|
-
]
|
|
316
|
-
};
|
|
317
|
-
const signature = await signTypedDataAsync({
|
|
318
|
-
domain: {
|
|
319
|
-
name: "HyperliquidSignTransaction",
|
|
320
|
-
version: "1",
|
|
321
|
-
chainId: isTestnet ? 421614 : 42161,
|
|
322
|
-
verifyingContract: "0x0000000000000000000000000000000000000000"
|
|
323
|
-
},
|
|
324
|
-
types,
|
|
325
|
-
primaryType: "HyperliquidTransaction:ApproveAgent",
|
|
326
|
-
message: {
|
|
327
|
-
hyperliquidChain: action.hyperliquidChain,
|
|
328
|
-
agentAddress: action.agentAddress,
|
|
329
|
-
agentName: "",
|
|
330
|
-
nonce: BigInt(nonce)
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
const r = `0x${signature.slice(2, 66)}`;
|
|
334
|
-
const s = `0x${signature.slice(66, 130)}`;
|
|
335
|
-
const v = parseInt(signature.slice(130, 132), 16);
|
|
336
|
-
const url = isTestnet ? "https://api.hyperliquid-testnet.xyz/exchange" : "https://api.hyperliquid.xyz/exchange";
|
|
337
|
-
const res = await fetch(url, {
|
|
338
|
-
method: "POST",
|
|
339
|
-
headers: { "Content-Type": "application/json" },
|
|
340
|
-
body: JSON.stringify({
|
|
341
|
-
action,
|
|
342
|
-
nonce,
|
|
343
|
-
signature: { r, s, v }
|
|
344
|
-
})
|
|
345
|
-
});
|
|
346
|
-
const body = await res.json();
|
|
347
|
-
if (body?.status !== "ok") {
|
|
348
|
-
throw new Error(`approveAgent failed: ${JSON.stringify(body)}`);
|
|
349
|
-
}
|
|
350
|
-
const stored = {
|
|
351
|
-
privateKey,
|
|
352
|
-
address: agentAddress,
|
|
353
|
-
approvedAt: Date.now()
|
|
354
|
-
};
|
|
355
|
-
saveAgent(eoaAddress, stored);
|
|
356
|
-
setAgent(stored);
|
|
357
|
-
},
|
|
358
|
-
[eoaAddress, config.isTestnet]
|
|
359
|
-
);
|
|
368
|
+
}, [
|
|
369
|
+
authMethod,
|
|
370
|
+
user,
|
|
371
|
+
agent,
|
|
372
|
+
eoaAddress,
|
|
373
|
+
config.isTestnet,
|
|
374
|
+
tgClient,
|
|
375
|
+
authDataMap
|
|
376
|
+
]);
|
|
360
377
|
const handleClearAgent = useCallback(() => {
|
|
361
378
|
if (eoaAddress) {
|
|
362
379
|
clearAgent(eoaAddress);
|
|
363
380
|
setAgent(null);
|
|
364
381
|
}
|
|
365
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
|
+
);
|
|
366
451
|
const [loginModalOpen, setLoginModalOpen] = useState(false);
|
|
367
452
|
const openLoginModal = useCallback(() => setLoginModalOpen(true), []);
|
|
368
453
|
const closeLoginModal = useCallback(() => setLoginModalOpen(false), []);
|
|
@@ -371,42 +456,115 @@ function HypurrConnectProvider({
|
|
|
371
456
|
localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(data));
|
|
372
457
|
setEoaAddress(null);
|
|
373
458
|
setAgent(null);
|
|
459
|
+
setEoaError(null);
|
|
374
460
|
}, []);
|
|
375
|
-
const
|
|
461
|
+
const connectEoa = useCallback((address) => {
|
|
376
462
|
setEoaAddress(address);
|
|
377
463
|
setTgLoginData(null);
|
|
378
464
|
setTgUser(null);
|
|
379
465
|
setTgError(null);
|
|
466
|
+
setEoaError(null);
|
|
380
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
|
+
}
|
|
381
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
|
+
);
|
|
382
531
|
const logout = useCallback(() => {
|
|
383
532
|
setTgLoginData(null);
|
|
384
533
|
setTgUser(null);
|
|
385
534
|
setTgError(null);
|
|
386
535
|
setEoaAddress(null);
|
|
387
536
|
setAgent(null);
|
|
537
|
+
setEoaError(null);
|
|
388
538
|
localStorage.removeItem(TELEGRAM_STORAGE_KEY);
|
|
389
539
|
}, []);
|
|
390
540
|
const value = useMemo(
|
|
391
541
|
() => ({
|
|
392
542
|
user,
|
|
393
543
|
isLoggedIn: !!user,
|
|
394
|
-
isLoading: tgLoading,
|
|
395
|
-
error: tgError,
|
|
544
|
+
isLoading: tgLoading || eoaLoading,
|
|
545
|
+
error: tgError ?? eoaError,
|
|
396
546
|
authMethod,
|
|
397
547
|
exchange,
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
548
|
+
wallets,
|
|
549
|
+
selectedWalletId,
|
|
550
|
+
selectWallet,
|
|
551
|
+
createWallet,
|
|
552
|
+
deleteWallet,
|
|
553
|
+
refreshWallets,
|
|
554
|
+
packs,
|
|
555
|
+
createWalletPack,
|
|
556
|
+
addPackLabel,
|
|
557
|
+
modifyPackLabel,
|
|
558
|
+
removePackLabel,
|
|
401
559
|
loginModalOpen,
|
|
402
560
|
openLoginModal,
|
|
403
561
|
closeLoginModal,
|
|
404
562
|
loginTelegram,
|
|
405
|
-
|
|
563
|
+
connectEoa,
|
|
564
|
+
approveAgent: approveAgentFn,
|
|
406
565
|
logout,
|
|
407
566
|
agent,
|
|
408
|
-
agentReady
|
|
409
|
-
approveAgent,
|
|
567
|
+
agentReady,
|
|
410
568
|
clearAgent: handleClearAgent,
|
|
411
569
|
botId: config.telegram?.botId ?? "",
|
|
412
570
|
authDataMap,
|
|
@@ -416,20 +574,31 @@ function HypurrConnectProvider({
|
|
|
416
574
|
[
|
|
417
575
|
user,
|
|
418
576
|
tgLoading,
|
|
577
|
+
eoaLoading,
|
|
419
578
|
tgError,
|
|
579
|
+
eoaError,
|
|
420
580
|
authMethod,
|
|
421
581
|
exchange,
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
582
|
+
wallets,
|
|
583
|
+
selectedWalletId,
|
|
584
|
+
selectWallet,
|
|
585
|
+
createWallet,
|
|
586
|
+
deleteWallet,
|
|
587
|
+
refreshWallets,
|
|
588
|
+
packs,
|
|
589
|
+
createWalletPack,
|
|
590
|
+
addPackLabel,
|
|
591
|
+
modifyPackLabel,
|
|
592
|
+
removePackLabel,
|
|
425
593
|
loginModalOpen,
|
|
426
594
|
openLoginModal,
|
|
427
595
|
closeLoginModal,
|
|
428
596
|
loginTelegram,
|
|
429
|
-
|
|
597
|
+
connectEoa,
|
|
598
|
+
approveAgentFn,
|
|
430
599
|
logout,
|
|
431
600
|
agent,
|
|
432
|
-
|
|
601
|
+
agentReady,
|
|
433
602
|
handleClearAgent,
|
|
434
603
|
config.telegram?.botId,
|
|
435
604
|
authDataMap,
|
|
@@ -454,18 +623,18 @@ import {
|
|
|
454
623
|
|
|
455
624
|
// src/icons/MetaMaskColorIcon.tsx
|
|
456
625
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
457
|
-
function MetaMaskColorIcon({
|
|
626
|
+
function MetaMaskColorIcon({ style }) {
|
|
458
627
|
return /* @__PURE__ */ jsxs(
|
|
459
628
|
"svg",
|
|
460
629
|
{
|
|
461
630
|
width: "24",
|
|
462
631
|
height: "24",
|
|
463
632
|
viewBox: "0 0 24 24",
|
|
464
|
-
|
|
633
|
+
style,
|
|
465
634
|
fill: "none",
|
|
466
635
|
xmlns: "http://www.w3.org/2000/svg",
|
|
467
636
|
children: [
|
|
468
|
-
/* @__PURE__ */ jsxs("g", {
|
|
637
|
+
/* @__PURE__ */ jsxs("g", { clipPath: "url(#clip0_2567_1088)", children: [
|
|
469
638
|
/* @__PURE__ */ jsx2(
|
|
470
639
|
"path",
|
|
471
640
|
{
|
|
@@ -532,14 +701,14 @@ function MetaMaskColorIcon({ className }) {
|
|
|
532
701
|
|
|
533
702
|
// src/icons/TelegramColorIcon.tsx
|
|
534
703
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
535
|
-
function TelegramColorIcon({
|
|
704
|
+
function TelegramColorIcon({ style }) {
|
|
536
705
|
return /* @__PURE__ */ jsxs2(
|
|
537
706
|
"svg",
|
|
538
707
|
{
|
|
539
708
|
width: "24",
|
|
540
709
|
height: "24",
|
|
541
710
|
viewBox: "0 0 24 24",
|
|
542
|
-
|
|
711
|
+
style,
|
|
543
712
|
fill: "none",
|
|
544
713
|
xmlns: "http://www.w3.org/2000/svg",
|
|
545
714
|
children: [
|
|
@@ -553,8 +722,8 @@ function TelegramColorIcon({ className }) {
|
|
|
553
722
|
/* @__PURE__ */ jsx3(
|
|
554
723
|
"path",
|
|
555
724
|
{
|
|
556
|
-
|
|
557
|
-
|
|
725
|
+
fillRule: "evenodd",
|
|
726
|
+
clipRule: "evenodd",
|
|
558
727
|
d: "M7.07426 11.905C9.69794 10.7619 11.4475 10.0083 12.3229 9.64417C14.8222 8.60458 15.3416 8.424 15.6801 8.41803C15.7546 8.41672 15.921 8.43517 16.0289 8.52267C16.1199 8.59655 16.145 8.69635 16.1569 8.7664C16.1689 8.83645 16.1839 8.99602 16.172 9.1207C16.0366 10.5438 15.4505 13.9973 15.1523 15.5912C15.0262 16.2657 14.7778 16.4918 14.5373 16.514C14.0146 16.562 13.6178 16.1686 13.1115 15.8367C12.3194 15.3175 11.8719 14.9943 11.103 14.4876C10.2145 13.902 10.7905 13.5802 11.2969 13.0542C11.4294 12.9166 13.7322 10.822 13.7768 10.632C13.7824 10.6082 13.7875 10.5196 13.7349 10.4729C13.6823 10.4261 13.6046 10.4421 13.5486 10.4548C13.4691 10.4728 12.2037 11.3092 9.75232 12.964C9.39313 13.2106 9.06779 13.3308 8.7763 13.3245C8.45496 13.3176 7.83681 13.1428 7.37729 12.9934C6.81366 12.8102 6.3657 12.7134 6.40471 12.4022C6.42503 12.2401 6.64821 12.0744 7.07426 11.905Z",
|
|
559
728
|
fill: "white"
|
|
560
729
|
}
|
|
@@ -569,8 +738,8 @@ function TelegramColorIcon({ className }) {
|
|
|
569
738
|
y2: "1789.65",
|
|
570
739
|
gradientUnits: "userSpaceOnUse",
|
|
571
740
|
children: [
|
|
572
|
-
/* @__PURE__ */ jsx3("stop", {
|
|
573
|
-
/* @__PURE__ */ jsx3("stop", { offset: "1",
|
|
741
|
+
/* @__PURE__ */ jsx3("stop", { stopColor: "#2AABEE" }),
|
|
742
|
+
/* @__PURE__ */ jsx3("stop", { offset: "1", stopColor: "#229ED9" })
|
|
574
743
|
]
|
|
575
744
|
}
|
|
576
745
|
) })
|
|
@@ -582,7 +751,67 @@ function TelegramColorIcon({ className }) {
|
|
|
582
751
|
// src/LoginModal.tsx
|
|
583
752
|
import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
584
753
|
var MOBILE_BREAKPOINT = 640;
|
|
585
|
-
var
|
|
754
|
+
var btnStyle = {
|
|
755
|
+
display: "flex",
|
|
756
|
+
height: 53,
|
|
757
|
+
width: "100%",
|
|
758
|
+
alignItems: "center",
|
|
759
|
+
gap: 12,
|
|
760
|
+
overflow: "hidden",
|
|
761
|
+
borderRadius: 6,
|
|
762
|
+
background: "rgba(255,255,255,0.05)",
|
|
763
|
+
padding: "0 24px",
|
|
764
|
+
fontSize: 14,
|
|
765
|
+
fontWeight: 600,
|
|
766
|
+
letterSpacing: "-0.01em",
|
|
767
|
+
color: "#fff",
|
|
768
|
+
cursor: "pointer",
|
|
769
|
+
border: "none",
|
|
770
|
+
transition: "background 150ms"
|
|
771
|
+
};
|
|
772
|
+
var btnHoverBg = { background: "rgba(255,255,255,0.1)" };
|
|
773
|
+
var backdropStyle = {
|
|
774
|
+
position: "fixed",
|
|
775
|
+
inset: 0,
|
|
776
|
+
zIndex: 100,
|
|
777
|
+
background: "rgba(0,0,0,0.6)",
|
|
778
|
+
backdropFilter: "blur(2px)",
|
|
779
|
+
WebkitBackdropFilter: "blur(2px)"
|
|
780
|
+
};
|
|
781
|
+
var modalWrapperStyle = {
|
|
782
|
+
position: "fixed",
|
|
783
|
+
inset: 0,
|
|
784
|
+
zIndex: 101,
|
|
785
|
+
display: "flex",
|
|
786
|
+
alignItems: "center",
|
|
787
|
+
justifyContent: "center",
|
|
788
|
+
padding: 16
|
|
789
|
+
};
|
|
790
|
+
var modalBoxStyle = {
|
|
791
|
+
display: "flex",
|
|
792
|
+
width: 400,
|
|
793
|
+
flexDirection: "column",
|
|
794
|
+
alignItems: "center",
|
|
795
|
+
gap: 16,
|
|
796
|
+
overflow: "hidden",
|
|
797
|
+
borderRadius: 12,
|
|
798
|
+
border: "1px solid rgba(255,255,255,0.1)",
|
|
799
|
+
background: "#282828",
|
|
800
|
+
padding: 24
|
|
801
|
+
};
|
|
802
|
+
var headingStyle = {
|
|
803
|
+
fontSize: 16,
|
|
804
|
+
fontWeight: 700,
|
|
805
|
+
letterSpacing: "-0.025em",
|
|
806
|
+
color: "#fff",
|
|
807
|
+
margin: 0
|
|
808
|
+
};
|
|
809
|
+
var dividerStyle = {
|
|
810
|
+
height: 1,
|
|
811
|
+
width: "100%",
|
|
812
|
+
background: "rgba(255,255,255,0.05)"
|
|
813
|
+
};
|
|
814
|
+
var iconSize = { width: 20, height: 20 };
|
|
586
815
|
var mobileQuery = typeof window !== "undefined" ? window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`) : null;
|
|
587
816
|
function subscribeMobile(cb) {
|
|
588
817
|
mobileQuery?.addEventListener("change", cb);
|
|
@@ -594,8 +823,23 @@ function getSnapshotMobile() {
|
|
|
594
823
|
function useIsMobile() {
|
|
595
824
|
return useSyncExternalStore(subscribeMobile, getSnapshotMobile, () => false);
|
|
596
825
|
}
|
|
826
|
+
function HoverButton({
|
|
827
|
+
onClick,
|
|
828
|
+
children
|
|
829
|
+
}) {
|
|
830
|
+
return /* @__PURE__ */ jsx4(
|
|
831
|
+
motion.button,
|
|
832
|
+
{
|
|
833
|
+
type: "button",
|
|
834
|
+
onClick,
|
|
835
|
+
style: btnStyle,
|
|
836
|
+
whileHover: btnHoverBg,
|
|
837
|
+
children
|
|
838
|
+
}
|
|
839
|
+
);
|
|
840
|
+
}
|
|
597
841
|
function LoginModal({ onConnectWallet, walletIcon }) {
|
|
598
|
-
const { loginTelegram, loginModalOpen, closeLoginModal, botId } =
|
|
842
|
+
const { loginTelegram, loginModalOpen, closeLoginModal, botId } = useHypurrConnectInternal();
|
|
599
843
|
const handleTelegramAuth = useCallback2(
|
|
600
844
|
(user) => {
|
|
601
845
|
loginTelegram(user);
|
|
@@ -642,22 +886,33 @@ function LoginModal({ onConnectWallet, walletIcon }) {
|
|
|
642
886
|
}, [botId]);
|
|
643
887
|
const isMobile = useIsMobile();
|
|
644
888
|
const modalContent = /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
645
|
-
/* @__PURE__ */ jsx4(
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
889
|
+
/* @__PURE__ */ jsx4(
|
|
890
|
+
"div",
|
|
891
|
+
{
|
|
892
|
+
style: {
|
|
893
|
+
display: "flex",
|
|
894
|
+
width: "100%",
|
|
895
|
+
flexDirection: "column",
|
|
896
|
+
alignItems: "center",
|
|
897
|
+
gap: 8,
|
|
898
|
+
overflow: "hidden"
|
|
899
|
+
},
|
|
900
|
+
children: /* @__PURE__ */ jsxs3(HoverButton, { onClick: openTelegramOAuth, children: [
|
|
901
|
+
/* @__PURE__ */ jsx4(TelegramColorIcon, { style: iconSize }),
|
|
902
|
+
"Telegram"
|
|
903
|
+
] })
|
|
904
|
+
}
|
|
905
|
+
),
|
|
906
|
+
/* @__PURE__ */ jsx4("div", { style: dividerStyle }),
|
|
650
907
|
/* @__PURE__ */ jsxs3(
|
|
651
|
-
|
|
908
|
+
HoverButton,
|
|
652
909
|
{
|
|
653
|
-
type: "button",
|
|
654
910
|
onClick: () => {
|
|
655
911
|
closeLoginModal();
|
|
656
912
|
onConnectWallet();
|
|
657
913
|
},
|
|
658
|
-
className: btnClass,
|
|
659
914
|
children: [
|
|
660
|
-
walletIcon ?? /* @__PURE__ */ jsx4(MetaMaskColorIcon, {
|
|
915
|
+
walletIcon ?? /* @__PURE__ */ jsx4(MetaMaskColorIcon, { style: iconSize }),
|
|
661
916
|
"Wallet"
|
|
662
917
|
]
|
|
663
918
|
}
|
|
@@ -667,7 +922,7 @@ function LoginModal({ onConnectWallet, walletIcon }) {
|
|
|
667
922
|
/* @__PURE__ */ jsx4(
|
|
668
923
|
motion.div,
|
|
669
924
|
{
|
|
670
|
-
|
|
925
|
+
style: backdropStyle,
|
|
671
926
|
initial: { opacity: 0 },
|
|
672
927
|
animate: { opacity: 1 },
|
|
673
928
|
exit: { opacity: 0 },
|
|
@@ -679,7 +934,7 @@ function LoginModal({ onConnectWallet, walletIcon }) {
|
|
|
679
934
|
/* @__PURE__ */ jsx4(
|
|
680
935
|
motion.div,
|
|
681
936
|
{
|
|
682
|
-
|
|
937
|
+
style: modalWrapperStyle,
|
|
683
938
|
initial: { opacity: 0 },
|
|
684
939
|
animate: { opacity: 1 },
|
|
685
940
|
exit: { opacity: 0 },
|
|
@@ -688,14 +943,14 @@ function LoginModal({ onConnectWallet, walletIcon }) {
|
|
|
688
943
|
children: /* @__PURE__ */ jsxs3(
|
|
689
944
|
motion.div,
|
|
690
945
|
{
|
|
691
|
-
|
|
946
|
+
style: modalBoxStyle,
|
|
692
947
|
initial: { opacity: 0, scale: 0.95, y: 10 },
|
|
693
948
|
animate: { opacity: 1, scale: 1, y: 0 },
|
|
694
949
|
exit: { opacity: 0, scale: 0.95, y: 10 },
|
|
695
950
|
transition: { duration: 0.2, ease: "easeOut" },
|
|
696
951
|
onClick: (e) => e.stopPropagation(),
|
|
697
952
|
children: [
|
|
698
|
-
/* @__PURE__ */ jsx4("p", {
|
|
953
|
+
/* @__PURE__ */ jsx4("p", { style: headingStyle, children: "Connect" }),
|
|
699
954
|
modalContent
|
|
700
955
|
]
|
|
701
956
|
}
|
|
@@ -705,6 +960,47 @@ function LoginModal({ onConnectWallet, walletIcon }) {
|
|
|
705
960
|
)
|
|
706
961
|
] })) });
|
|
707
962
|
}
|
|
963
|
+
var drawerSheetStyle = {
|
|
964
|
+
position: "fixed",
|
|
965
|
+
left: 0,
|
|
966
|
+
right: 0,
|
|
967
|
+
bottom: 0,
|
|
968
|
+
zIndex: 101,
|
|
969
|
+
display: "flex",
|
|
970
|
+
flexDirection: "column",
|
|
971
|
+
alignItems: "center",
|
|
972
|
+
gap: 16,
|
|
973
|
+
borderTopLeftRadius: 12,
|
|
974
|
+
borderTopRightRadius: 12,
|
|
975
|
+
borderLeft: "1px solid rgba(255,255,255,0.1)",
|
|
976
|
+
borderRight: "1px solid rgba(255,255,255,0.1)",
|
|
977
|
+
borderTop: "1px solid rgba(255,255,255,0.1)",
|
|
978
|
+
background: "#282828",
|
|
979
|
+
padding: "12px 24px max(24px, env(safe-area-inset-bottom))"
|
|
980
|
+
};
|
|
981
|
+
var drawerBgStyle = {
|
|
982
|
+
position: "absolute",
|
|
983
|
+
left: 0,
|
|
984
|
+
right: 0,
|
|
985
|
+
top: 0,
|
|
986
|
+
bottom: "-100vh",
|
|
987
|
+
zIndex: -1,
|
|
988
|
+
background: "#282828",
|
|
989
|
+
borderTopLeftRadius: 12,
|
|
990
|
+
borderTopRightRadius: 12
|
|
991
|
+
};
|
|
992
|
+
var grabHandleAreaStyle = {
|
|
993
|
+
width: "100%",
|
|
994
|
+
cursor: "grab",
|
|
995
|
+
paddingBottom: 4
|
|
996
|
+
};
|
|
997
|
+
var grabHandleStyle = {
|
|
998
|
+
margin: "0 auto",
|
|
999
|
+
height: 4,
|
|
1000
|
+
width: 100,
|
|
1001
|
+
borderRadius: 9999,
|
|
1002
|
+
background: "rgba(255,255,255,0.05)"
|
|
1003
|
+
};
|
|
708
1004
|
function MobileDrawer({
|
|
709
1005
|
children,
|
|
710
1006
|
onClose
|
|
@@ -724,7 +1020,7 @@ function MobileDrawer({
|
|
|
724
1020
|
/* @__PURE__ */ jsx4(
|
|
725
1021
|
motion.div,
|
|
726
1022
|
{
|
|
727
|
-
|
|
1023
|
+
style: backdropStyle,
|
|
728
1024
|
initial: { opacity: 0 },
|
|
729
1025
|
animate: { opacity: 1 },
|
|
730
1026
|
exit: { opacity: 0 },
|
|
@@ -736,7 +1032,7 @@ function MobileDrawer({
|
|
|
736
1032
|
/* @__PURE__ */ jsxs3(
|
|
737
1033
|
motion.div,
|
|
738
1034
|
{
|
|
739
|
-
|
|
1035
|
+
style: drawerSheetStyle,
|
|
740
1036
|
initial: { y: "100%" },
|
|
741
1037
|
animate: { y: 0 },
|
|
742
1038
|
exit: { y: "100%" },
|
|
@@ -746,9 +1042,9 @@ function MobileDrawer({
|
|
|
746
1042
|
dragElastic: { top: 0, bottom: 0.4 },
|
|
747
1043
|
onDragEnd: handleDragEnd,
|
|
748
1044
|
children: [
|
|
749
|
-
/* @__PURE__ */ jsx4("div", {
|
|
750
|
-
/* @__PURE__ */ jsx4("div", {
|
|
751
|
-
/* @__PURE__ */ jsx4("p", {
|
|
1045
|
+
/* @__PURE__ */ jsx4("div", { style: drawerBgStyle }),
|
|
1046
|
+
/* @__PURE__ */ jsx4("div", { style: grabHandleAreaStyle, children: /* @__PURE__ */ jsx4("div", { style: grabHandleStyle }) }),
|
|
1047
|
+
/* @__PURE__ */ jsx4("p", { style: headingStyle, children: "Connect" }),
|
|
752
1048
|
children
|
|
753
1049
|
]
|
|
754
1050
|
},
|