@hfunlabs/hypurr-connect 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,8 +4,8 @@ import {
4
4
  type IRequestTransport,
5
5
  } from "@hfunlabs/hyperliquid";
6
6
  import {
7
- PrivateKeySigner,
8
7
  signUserSignedAction,
8
+ type AbstractViemLocalAccount,
9
9
  } from "@hfunlabs/hyperliquid/signing";
10
10
  import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
11
11
  import type { TelegramUserResponse } from "hypurr-grpc/ts/hypurr/telegram/telegram_service";
@@ -36,9 +36,12 @@ import {
36
36
  } from "./agent";
37
37
  import { createStaticClient, createTelegramClient } from "./grpc";
38
38
  import { GrpcExchangeTransport } from "./GrpcExchangeTransport";
39
+ import { PrivateKeySigner } from "./privateKeySigner";
39
40
  import type {
40
41
  AuthMethod,
41
42
  EoaSigner,
43
+ EvmTransactionRequest,
44
+ Hex,
42
45
  HypurrConnectConfig,
43
46
  HypurrConnectState,
44
47
  HypurrUser,
@@ -49,8 +52,6 @@ import type {
49
52
  /** @internal context value — extends the public type with fields used only by library internals */
50
53
  interface InternalConnectState extends HypurrConnectState {
51
54
  loginTelegram: () => void;
52
- botUsername: string;
53
- useWidget: boolean;
54
55
  }
55
56
 
56
57
  const TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-jwt";
@@ -59,6 +60,8 @@ const TELEGRAM_AUTH_STATE_KEY = "hypurr-connect-auth-state";
59
60
  const TELEGRAM_AUTH_MESSAGE = "hypurr-connect:telegram-auth";
60
61
  const DEFAULT_AUTH_HUB_URL = "https://auth.hypurr.fun/login";
61
62
  const DEFAULT_MEDIA_URL = "https://media.hypurr.fun";
63
+ const IGNORED_EXTERNAL_SIGNATURE =
64
+ "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b" as const;
62
65
  const DEFAULT_TELEGRAM_SCOPES = [
63
66
  "telegram:user:read",
64
67
  "telegram:wallet:read",
@@ -68,8 +71,20 @@ const DEFAULT_TELEGRAM_SCOPES = [
68
71
  "telegram:cabal:read",
69
72
  "telegram:cabal:write",
70
73
  "telegram:agent:write",
74
+ "telegram:support:read",
75
+ "telegram:support:write",
71
76
  ];
72
77
 
78
+ function createExternalSigningWallet(address: Hex): AbstractViemLocalAccount {
79
+ return {
80
+ address,
81
+ signTypedData(params) {
82
+ void params;
83
+ return Promise.resolve(IGNORED_EXTERNAL_SIGNATURE);
84
+ },
85
+ };
86
+ }
87
+
73
88
  function isInvalidTelegramAuthError(err: unknown): boolean {
74
89
  const msg =
75
90
  err instanceof Error
@@ -86,6 +101,60 @@ function normalizeMediaUrl(mediaUrl?: string): string {
86
101
  return (mediaUrl?.trim() || DEFAULT_MEDIA_URL).replace(/\/+$/, "");
87
102
  }
88
103
 
104
+ function isAddress(value?: string | null): value is Hex {
105
+ return !!value && /^0x[a-fA-F0-9]{40}$/.test(value);
106
+ }
107
+
108
+ function getRawSignedTransaction(result: unknown): Hex | null {
109
+ if (typeof result === "string" && result.startsWith("0x")) {
110
+ return result as Hex;
111
+ }
112
+ if (!result || typeof result !== "object") return null;
113
+
114
+ const record = result as Record<string, unknown>;
115
+ const candidates = [
116
+ record.raw,
117
+ record.rawTransaction,
118
+ record.signedTransaction,
119
+ record.serializedTransaction,
120
+ record.result,
121
+ ];
122
+ const raw = candidates.find(
123
+ (value): value is string =>
124
+ typeof value === "string" && value.startsWith("0x"),
125
+ );
126
+ return raw ? (raw as Hex) : null;
127
+ }
128
+
129
+ function decodeJsonBytes(bytes: Uint8Array): unknown {
130
+ const text = new TextDecoder().decode(bytes);
131
+ if (!text) return null;
132
+ try {
133
+ return JSON.parse(text);
134
+ } catch {
135
+ return text;
136
+ }
137
+ }
138
+
139
+ function encodeJsonBytes(value: unknown): Uint8Array {
140
+ return new TextEncoder().encode(JSON.stringify(value));
141
+ }
142
+
143
+ function withExpectedFrom(
144
+ transaction: EvmTransactionRequest,
145
+ address: Hex,
146
+ ): EvmTransactionRequest {
147
+ if (
148
+ transaction.from &&
149
+ transaction.from.toLowerCase() !== address.toLowerCase()
150
+ ) {
151
+ throw new Error(
152
+ "[HypurrConnect] EVM transaction `from` does not match the connected wallet.",
153
+ );
154
+ }
155
+ return transaction.from ? transaction : { ...transaction, from: address };
156
+ }
157
+
89
158
  function currentReturnTo(): string {
90
159
  const url = new URL(window.location.href);
91
160
  for (const param of [
@@ -113,9 +182,7 @@ function normalizeScopes(scope?: string | string[]): string {
113
182
  return scope?.trim() || DEFAULT_TELEGRAM_SCOPES.join(" ");
114
183
  }
115
184
 
116
- function isTelegramAuthMessage(
117
- data: unknown,
118
- ): data is {
185
+ function isTelegramAuthMessage(data: unknown): data is {
119
186
  type: typeof TELEGRAM_AUTH_MESSAGE;
120
187
  token: string;
121
188
  state: string;
@@ -435,9 +502,12 @@ export function HypurrConnectProvider({
435
502
  displayName: tgUser.telegramUsername
436
503
  ? `@${tgUser.telegramUsername}`
437
504
  : `Telegram ${tgUser.telegramId}`,
438
- photoUrl: tgUser.pictureFileId
439
- ? `${mediaUrl}/${tgUser.pictureFileId}`
440
- : undefined,
505
+ photoUrl: (() => {
506
+ // `pictureFileId` is read at runtime; the schema may not type it.
507
+ const fileId = (tgUser as unknown as { pictureFileId?: string })
508
+ .pictureFileId;
509
+ return fileId ? `${mediaUrl}/${fileId}` : undefined;
510
+ })(),
441
511
  authMethod: "telegram",
442
512
  telegramId: String(tgUser.telegramId),
443
513
  hfunScore: tgUser?.reputation?.hfunScore,
@@ -463,7 +533,7 @@ export function HypurrConnectProvider({
463
533
  ]);
464
534
 
465
535
  // ── Exchange client ──────────────────────────────────────────
466
- // Telegram: GrpcExchangeTransport → HyperliquidCoreAction (server signs)
536
+ // Telegram: dummy local wallet → GrpcExchangeTransport → HyperliquidCoreAction (server signs)
467
537
  // EOA: dual wallet — agent key for L1 actions, master signer for user-signed
468
538
  // actions (transfers, withdrawals, etc.). The dual wallet inspects the
469
539
  // EIP-712 domain name to decide which key signs each request.
@@ -509,8 +579,7 @@ export function HypurrConnectProvider({
509
579
  });
510
580
  return new ExchangeClient({
511
581
  transport,
512
- externalSigning: true,
513
- userAddress: user.address as `0x${string}`,
582
+ wallet: createExternalSigningWallet(user.address as Hex),
514
583
  });
515
584
  }
516
585
 
@@ -530,8 +599,7 @@ export function HypurrConnectProvider({
530
599
  };
531
600
  return new ExchangeClient({
532
601
  transport: noAgentTransport,
533
- externalSigning: true,
534
- userAddress: eoaAddress,
602
+ wallet: createExternalSigningWallet(eoaAddress),
535
603
  });
536
604
  }
537
605
 
@@ -671,20 +739,9 @@ export function HypurrConnectProvider({
671
739
  // Dual wallet: routes signing based on the EIP-712 domain.
672
740
  // "Exchange" domain → L1 action → agent key signs (auto-provisions if needed).
673
741
  // "HyperliquidSignTransaction" domain → user-signed → master wallet (popup).
674
- const dualWallet = {
742
+ const dualWallet: AbstractViemLocalAccount = {
675
743
  address: ownerAddress,
676
- async signTypedData(params: {
677
- domain: {
678
- name?: string;
679
- version?: string;
680
- chainId?: number;
681
- verifyingContract?: `0x${string}`;
682
- salt?: `0x${string}`;
683
- };
684
- types: Record<string, { name: string; type: string }[]>;
685
- primaryType: string;
686
- message: Record<string, unknown>;
687
- }): Promise<`0x${string}`> {
744
+ async signTypedData(params): Promise<`0x${string}`> {
688
745
  if (params.domain.name === "HyperliquidSignTransaction") {
689
746
  const signer = signerRef.current;
690
747
  if (!signer) {
@@ -731,6 +788,95 @@ export function HypurrConnectProvider({
731
788
  }
732
789
  }, [eoaAddress]);
733
790
 
791
+ const signEvmTransaction = useCallback(
792
+ async (transaction: EvmTransactionRequest): Promise<Hex> => {
793
+ if (authMethod === "eoa") {
794
+ if (!eoaAddress) {
795
+ throw new Error("[HypurrConnect] No EOA wallet connected.");
796
+ }
797
+
798
+ const signer = eoaSignerRef.current;
799
+ if (!signer) {
800
+ throw new Error(
801
+ "[HypurrConnect] No EOA signer available. Pass a signer to connectEoa(address, signer).",
802
+ );
803
+ }
804
+
805
+ const tx = withExpectedFrom(transaction, eoaAddress);
806
+ const result = signer.signTransaction
807
+ ? await signer.signTransaction(tx)
808
+ : signer.request
809
+ ? await signer.request({
810
+ method: "eth_signTransaction",
811
+ params: [tx],
812
+ })
813
+ : null;
814
+ const rawTransaction = getRawSignedTransaction(result);
815
+ if (!rawTransaction) {
816
+ throw new Error(
817
+ "[HypurrConnect] EOA signer did not return a raw transaction.",
818
+ );
819
+ }
820
+ return rawTransaction;
821
+ }
822
+
823
+ if (authMethod === "telegram") {
824
+ if (!telegramRpcOptions) {
825
+ throw new Error("[HypurrConnect] No Telegram RPC session available.");
826
+ }
827
+ if (!selectedWallet) {
828
+ throw new Error("[HypurrConnect] No Telegram wallet selected.");
829
+ }
830
+ if (
831
+ selectedWallet.id <= 0 ||
832
+ selectedWallet.isReadOnly ||
833
+ selectedWallet.isAgent
834
+ ) {
835
+ throw new Error(
836
+ "[HypurrConnect] Select a Telegram private-key wallet to sign EVM transactions.",
837
+ );
838
+ }
839
+ if (!isAddress(selectedWallet.ethereumAddress)) {
840
+ throw new Error(
841
+ "[HypurrConnect] Selected Telegram wallet does not have a valid EVM address.",
842
+ );
843
+ }
844
+
845
+ const tx = withExpectedFrom(
846
+ transaction,
847
+ selectedWallet.ethereumAddress,
848
+ );
849
+ try {
850
+ const signResponse = await tgClient.eVMSignTransaction(
851
+ {
852
+ authData: {},
853
+ walletId: selectedWallet.id,
854
+ params: encodeJsonBytes(tx),
855
+ },
856
+ telegramRpcOptions,
857
+ );
858
+ const rawTransaction = getRawSignedTransaction(
859
+ decodeJsonBytes(signResponse.response.result),
860
+ );
861
+ if (!rawTransaction) {
862
+ throw new Error(
863
+ "[HypurrConnect] Telegram signer did not return a raw transaction.",
864
+ );
865
+ }
866
+ return rawTransaction;
867
+ } catch (err) {
868
+ if (isInvalidTelegramAuthError(err)) {
869
+ onInvalidAuthRef.current?.();
870
+ }
871
+ throw err;
872
+ }
873
+ }
874
+
875
+ throw new Error("[HypurrConnect] No wallet connected.");
876
+ },
877
+ [authMethod, eoaAddress, selectedWallet, telegramRpcOptions, tgClient],
878
+ );
879
+
734
880
  // ── Wallet management (Telegram only) ───────────────────────
735
881
  const createWallet = useCallback(
736
882
  async (name: string): Promise<HyperliquidWallet> => {
@@ -767,6 +913,21 @@ export function HypurrConnectProvider({
767
913
  [tgClient, telegramRpcOptions, selectedWalletId, wallets, refreshWallets],
768
914
  );
769
915
 
916
+ const renameWallet = useCallback(
917
+ async (walletId: number, name: string): Promise<void> => {
918
+ await tgClient.hyperliquidWalletUpdate(
919
+ {
920
+ authData: {},
921
+ walletId,
922
+ name,
923
+ },
924
+ telegramRpcOptions,
925
+ );
926
+ refreshWallets();
927
+ },
928
+ [tgClient, telegramRpcOptions, refreshWallets],
929
+ );
930
+
770
931
  const createWalletPack = useCallback(
771
932
  async (name: string): Promise<number> => {
772
933
  const { response } = await tgClient.telegramChatWalletPackCreate(
@@ -988,16 +1149,16 @@ export function HypurrConnectProvider({
988
1149
  const state = randomState();
989
1150
  sessionStorage.setItem(TELEGRAM_AUTH_STATE_KEY, state);
990
1151
 
991
- const configuredReturnTo = config.telegram.returnTo;
1152
+ const configuredReturnTo = config.telegram?.returnTo;
992
1153
  const returnTo =
993
1154
  typeof configuredReturnTo === "function"
994
1155
  ? configuredReturnTo()
995
1156
  : configuredReturnTo || currentReturnTo();
996
1157
 
997
- const authUrl = new URL(config.telegram.authHubUrl || DEFAULT_AUTH_HUB_URL);
1158
+ const authUrl = new URL(config.telegram?.authHubUrl || DEFAULT_AUTH_HUB_URL);
998
1159
  authUrl.searchParams.set("return_to", returnTo);
999
1160
  authUrl.searchParams.set("state", state);
1000
- authUrl.searchParams.set("scope", normalizeScopes(config.telegram.scope));
1161
+ authUrl.searchParams.set("scope", normalizeScopes(config.telegram?.scope));
1001
1162
 
1002
1163
  const width = 520;
1003
1164
  const height = 720;
@@ -1023,9 +1184,9 @@ export function HypurrConnectProvider({
1023
1184
 
1024
1185
  window.location.assign(authUrl.toString());
1025
1186
  }, [
1026
- config.telegram.authHubUrl,
1027
- config.telegram.returnTo,
1028
- config.telegram.scope,
1187
+ config.telegram?.authHubUrl,
1188
+ config.telegram?.returnTo,
1189
+ config.telegram?.scope,
1029
1190
  ]);
1030
1191
 
1031
1192
  const connectEoa = useCallback(
@@ -1058,7 +1219,13 @@ export function HypurrConnectProvider({
1058
1219
  );
1059
1220
  }
1060
1221
 
1061
- eoaSignerRef.current = { signTypedData: signTypedDataAsync, chainId };
1222
+ const currentSigner = eoaSignerRef.current;
1223
+ eoaSignerRef.current = {
1224
+ signTypedData: signTypedDataAsync,
1225
+ signTransaction: currentSigner?.signTransaction,
1226
+ request: currentSigner?.request,
1227
+ chainId,
1228
+ };
1062
1229
 
1063
1230
  setEoaLoading(true);
1064
1231
  setEoaError(null);
@@ -1182,6 +1349,7 @@ export function HypurrConnectProvider({
1182
1349
 
1183
1350
  createWallet,
1184
1351
  deleteWallet,
1352
+ renameWallet,
1185
1353
  refreshWallets,
1186
1354
 
1187
1355
  packs,
@@ -1203,6 +1371,7 @@ export function HypurrConnectProvider({
1203
1371
 
1204
1372
  loginTelegram,
1205
1373
  connectEoa,
1374
+ signEvmTransaction,
1206
1375
  approveAgent: approveAgentFn,
1207
1376
  logout,
1208
1377
 
@@ -1210,10 +1379,6 @@ export function HypurrConnectProvider({
1210
1379
  agentReady,
1211
1380
  clearAgent: handleClearAgent,
1212
1381
 
1213
- botId: config.telegram?.botId ?? "",
1214
- botUsername: config.telegram?.botUsername ?? "",
1215
- useWidget: config.telegram?.useWidget ?? false,
1216
-
1217
1382
  authDataMap,
1218
1383
  authToken: tgAuthToken,
1219
1384
  telegramRpcOptions,
@@ -1233,6 +1398,7 @@ export function HypurrConnectProvider({
1233
1398
  selectWallet,
1234
1399
  createWallet,
1235
1400
  deleteWallet,
1401
+ renameWallet,
1236
1402
  refreshWallets,
1237
1403
  packs,
1238
1404
  createWalletPack,
@@ -1249,14 +1415,12 @@ export function HypurrConnectProvider({
1249
1415
  closeLoginModal,
1250
1416
  loginTelegram,
1251
1417
  connectEoa,
1418
+ signEvmTransaction,
1252
1419
  approveAgentFn,
1253
1420
  logout,
1254
1421
  agent,
1255
1422
  agentReady,
1256
1423
  handleClearAgent,
1257
- config.telegram?.botId,
1258
- config.telegram?.botUsername,
1259
- config.telegram?.useWidget,
1260
1424
  authDataMap,
1261
1425
  tgAuthToken,
1262
1426
  telegramRpcOptions,