@hfunlabs/hypurr-connect 0.1.10 → 0.1.12

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 CHANGED
@@ -119,6 +119,7 @@ function AppShell() {
119
119
  ```typescript
120
120
  interface HypurrConnectConfig {
121
121
  grpcUrl?: string; // gRPC-web base URL (default: https://grpc.hypurr.fun)
122
+ mediaUrl?: string; // Media base URL (default: https://media.hypurr.fun)
122
123
  grpcTimeout?: number; // Request timeout in ms (default: 15000)
123
124
  isTestnet?: boolean; // Use testnet endpoints (default: false)
124
125
  telegram: {
@@ -219,6 +220,24 @@ connectEoa(address); // no signer — only L1 actions after manual approval
219
220
  await approveAgent(signTypedDataAsync, chainId);
220
221
  ```
221
222
 
223
+ ### EVM Transaction Signing
224
+
225
+ `signEvmTransaction(transaction)` signs a JSON-RPC-style EVM transaction and returns the raw serialized transaction. In Telegram mode it calls the `EVMSignTransaction` gRPC endpoint for the selected private-key wallet; in EOA mode it calls the `signTransaction` or EIP-1193 `request` function supplied to `connectEoa`.
226
+
227
+ ```tsx
228
+ const { signEvmTransaction } = useHypurrConnect();
229
+
230
+ const raw = await signEvmTransaction({
231
+ to: "0x0000000000000000000000000000000000000000",
232
+ data: "0x",
233
+ value: "0x0",
234
+ chainId: "0x66eee",
235
+ nonce: "0x1",
236
+ gas: "0x5208",
237
+ gasPrice: "0x3b9aca00",
238
+ });
239
+ ```
240
+
222
241
  ### Using the Exchange Client
223
242
 
224
243
  Once authenticated, the `exchange` object from `useHypurrConnect()` is a fully functional `ExchangeClient` from `@hfunlabs/hyperliquid`. For EOA users with a signer, it handles **both** L1 and user-signed actions transparently:
@@ -380,6 +399,7 @@ Returns the full auth and exchange state. Throws if used outside `HypurrConnectP
380
399
  | `openLoginModal` | `() => void` | Show the login modal |
381
400
  | `closeLoginModal` | `() => void` | Hide the login modal |
382
401
  | `connectEoa` | `(address: \`0x\${string}\`, signer?: EoaSigner) => void` | Connect EOA wallet (sync); pass signer to enable user-signed actions and auto-provisioning |
402
+ | `signEvmTransaction` | `(transaction: EvmTransactionRequest) => Promise<\`0x\${string}\`>` | Sign an EVM transaction through EOA wallet provider or Telegram RPC |
383
403
  | `approveAgent` | `(signTypedDataAsync: SignTypedDataFn, chainId: number) => Promise<void>` | Approve a named agent key (async, triggers wallet prompt) |
384
404
  | `logout` | `() => void` | Clear all auth state and localStorage |
385
405
  | `agent` | `StoredAgent \| null` | Current agent key (EOA flow only) |
@@ -472,11 +492,13 @@ Encapsulates the master wallet's EIP-712 signing function and chain ID. Pass to
472
492
  ```typescript
473
493
  interface EoaSigner {
474
494
  signTypedData: SignTypedDataFn;
495
+ signTransaction?: (transaction: EvmTransactionRequest) => Promise<unknown>;
496
+ request?: (args: { method: string; params?: unknown[] }) => Promise<unknown>;
475
497
  chainId: number;
476
498
  }
477
499
  ```
478
500
 
479
- #### `createEoaSigner(signTypedData, chainId): EoaSigner`
501
+ #### `createEoaSigner(signTypedData, chainId, options?): EoaSigner`
480
502
 
481
503
  Helper to create an `EoaSigner` from wagmi's `signTypedDataAsync` (or any compatible function). Accepts either a direct function or a `{ current: Function }` ref to avoid stale closures with React hooks:
482
504
 
@@ -488,6 +510,11 @@ const signer = createEoaSigner(signerRef, chainId);
488
510
 
489
511
  // With a direct function (fine for stable references)
490
512
  const signer = createEoaSigner(signTypedDataAsync, chainId);
513
+
514
+ // With an EIP-1193 provider for EVM transaction signing
515
+ const signer = createEoaSigner(signTypedDataAsync, chainId, {
516
+ request: provider.request.bind(provider),
517
+ });
491
518
  ```
492
519
 
493
520
  #### `SignTypedDataFn`
package/dist/index.d.ts CHANGED
@@ -11,10 +11,13 @@ import { TelegramChatWalletPack } from 'hypurr-grpc/ts/hypurr/user';
11
11
  export { TelegramChatWalletPack } from 'hypurr-grpc/ts/hypurr/user';
12
12
  import { HyperliquidWallet } from 'hypurr-grpc/ts/hypurr/wallet';
13
13
  export { HyperliquidWallet } from 'hypurr-grpc/ts/hypurr/wallet';
14
+ import { AbstractViemLocalAccount } from '@hfunlabs/hyperliquid/signing';
14
15
 
15
16
  interface HypurrConnectConfig {
16
17
  /** gRPC-web base URL. Defaults to https://grpc.hypurr.fun. */
17
18
  grpcUrl?: string;
19
+ /** Media base URL for user-uploaded assets. Defaults to https://media.hypurr.fun. */
20
+ mediaUrl?: string;
18
21
  grpcTimeout?: number;
19
22
  isTestnet?: boolean;
20
23
  /** Polling interval in ms for TWAP/Scale session updates. Default 5000. Set 0 to disable. */
@@ -71,11 +74,41 @@ type SignTypedDataFn = (params: {
71
74
  primaryType: string;
72
75
  message: Record<string, unknown>;
73
76
  }) => Promise<`0x${string}`>;
77
+ type Hex = `0x${string}`;
78
+ interface EvmTransactionRequest {
79
+ from?: Hex;
80
+ to?: Hex;
81
+ gas?: Hex;
82
+ gasPrice?: Hex;
83
+ value?: Hex;
84
+ data?: Hex;
85
+ nonce?: Hex;
86
+ chainId?: Hex;
87
+ maxFeePerGas?: Hex;
88
+ maxPriorityFeePerGas?: Hex;
89
+ type?: Hex;
90
+ accessList?: unknown;
91
+ [key: string]: unknown;
92
+ }
93
+ type EvmRequestFn = (args: {
94
+ method: string;
95
+ params?: unknown[];
96
+ }) => Promise<unknown>;
97
+ type SignEvmTransactionFn = (transaction: EvmTransactionRequest) => Promise<Hex>;
98
+ type EoaSignTransactionFn = (transaction: EvmTransactionRequest) => Promise<unknown>;
74
99
  /** Wallet signer provided at EOA connect time for user-signed actions. */
75
100
  interface EoaSigner {
76
101
  signTypedData: SignTypedDataFn;
102
+ /** Optional raw EVM transaction signer. Used by `signEvmTransaction()` in EOA mode. */
103
+ signTransaction?: EoaSignTransactionFn;
104
+ /** Optional EIP-1193 provider request function. Used as a fallback for `eth_signTransaction`. */
105
+ request?: EvmRequestFn;
77
106
  chainId: number;
78
107
  }
108
+ interface EoaSignerOptions {
109
+ signTransaction?: EoaSignTransactionFn;
110
+ request?: EvmRequestFn;
111
+ }
79
112
  /**
80
113
  * Create an {@link EoaSigner} from any EIP-712 signing function.
81
114
  *
@@ -101,7 +134,7 @@ interface EoaSigner {
101
134
  */
102
135
  declare function createEoaSigner(signTypedDataAsync: ((args: Record<string, unknown>) => Promise<`0x${string}`>) | {
103
136
  current: (args: Record<string, unknown>) => Promise<`0x${string}`>;
104
- }, chainId: number): EoaSigner;
137
+ }, chainId: number, options?: EoaSignerOptions): EoaSigner;
105
138
  type TwapCreateParams = Omit<HyperliquidTwapCreateRequest, "authData" | "walletId">;
106
139
  type TwapModifyParams = Omit<HyperliquidTwapModifyRequest, "authData" | "walletId">;
107
140
  type ScaleCreateParams = Omit<HyperliquidScaleCreateRequest, "authData" | "walletId">;
@@ -143,6 +176,7 @@ interface HypurrConnectState {
143
176
  openLoginModal: () => void;
144
177
  closeLoginModal: () => void;
145
178
  connectEoa: (address: `0x${string}`, signer?: EoaSigner) => void;
179
+ signEvmTransaction: SignEvmTransactionFn;
146
180
  approveAgent: (signTypedDataAsync: SignTypedDataFn, chainId: number) => Promise<void>;
147
181
  logout: () => void;
148
182
  agent: StoredAgent | null;
@@ -199,4 +233,17 @@ declare class GrpcExchangeTransport implements IRequestTransport {
199
233
  declare function createTelegramClient(config: HypurrConnectConfig): TelegramClient;
200
234
  declare function createStaticClient(config: HypurrConnectConfig): StaticClient;
201
235
 
202
- export { type AuthMethod, type EoaSigner, GrpcExchangeTransport, type GrpcExchangeTransportConfig, type HypurrConnectConfig, HypurrConnectProvider, type HypurrConnectState, type HypurrUser, LoginModal, type LoginModalProps, type ScaleCreateParams, type SignTypedDataFn, type StoredAgent, type TelegramLoginData, type TwapCreateParams, type TwapModifyParams, createEoaSigner, createStaticClient, createTelegramClient, useHypurrConnect };
236
+ type SignTypedDataParams = Parameters<AbstractViemLocalAccount["signTypedData"]>[0];
237
+ /**
238
+ * Compatibility wrapper for SDK versions that removed PrivateKeySigner.
239
+ *
240
+ * It exposes the viem local-account shape accepted by the Hyperliquid SDK.
241
+ */
242
+ declare class PrivateKeySigner implements AbstractViemLocalAccount {
243
+ #private;
244
+ readonly address: Hex;
245
+ constructor(privateKey: string);
246
+ signTypedData(params: SignTypedDataParams): Promise<Hex>;
247
+ }
248
+
249
+ export { type AuthMethod, type EoaSignTransactionFn, type EoaSigner, type EoaSignerOptions, type EvmRequestFn, type EvmTransactionRequest, GrpcExchangeTransport, type GrpcExchangeTransportConfig, type Hex, type HypurrConnectConfig, HypurrConnectProvider, type HypurrConnectState, type HypurrUser, LoginModal, type LoginModalProps, PrivateKeySigner, type ScaleCreateParams, type SignEvmTransactionFn, type SignTypedDataFn, type StoredAgent, type TelegramLoginData, type TwapCreateParams, type TwapModifyParams, createEoaSigner, createStaticClient, createTelegramClient, useHypurrConnect };
package/dist/index.js CHANGED
@@ -4,7 +4,6 @@ import {
4
4
  HttpTransport
5
5
  } from "@hfunlabs/hyperliquid";
6
6
  import {
7
- PrivateKeySigner,
8
7
  signUserSignedAction
9
8
  } from "@hfunlabs/hyperliquid/signing";
10
9
  import {
@@ -17,6 +16,25 @@ import {
17
16
  useState
18
17
  } from "react";
19
18
 
19
+ // src/privateKeySigner.ts
20
+ import { privateKeyToAccount } from "viem/accounts";
21
+ var PrivateKeySigner = class {
22
+ #account;
23
+ address;
24
+ constructor(privateKey) {
25
+ this.#account = privateKeyToAccount(normalizePrivateKey(privateKey));
26
+ this.address = this.#account.address;
27
+ }
28
+ signTypedData(params) {
29
+ return this.#account.signTypedData(
30
+ params
31
+ );
32
+ }
33
+ };
34
+ function normalizePrivateKey(privateKey) {
35
+ return privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
36
+ }
37
+
20
38
  // src/agent.ts
21
39
  var AGENT_NAME = "hypurr-connect";
22
40
  var AGENT_STORAGE_PREFIX = "hypurr-connect-agent";
@@ -41,8 +59,7 @@ async function generateAgentKey() {
41
59
  const bytes = crypto.getRandomValues(new Uint8Array(32));
42
60
  const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
43
61
  const privateKey = `0x${hex}`;
44
- const { PrivateKeySigner: PrivateKeySigner2 } = await import("@hfunlabs/hyperliquid/signing");
45
- const signer = new PrivateKeySigner2(privateKey);
62
+ const signer = new PrivateKeySigner(privateKey);
46
63
  return { privateKey, address: signer.address };
47
64
  }
48
65
  async function fetchActiveAgent(userAddress, isTestnet) {
@@ -186,7 +203,9 @@ var TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-jwt";
186
203
  var LEGACY_TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-user";
187
204
  var TELEGRAM_AUTH_STATE_KEY = "hypurr-connect-auth-state";
188
205
  var TELEGRAM_AUTH_MESSAGE = "hypurr-connect:telegram-auth";
189
- var DEFAULT_AUTH_HUB_URL = "https://127.0.0.1:443/login";
206
+ var DEFAULT_AUTH_HUB_URL = "https://auth.hypurr.fun/login";
207
+ var DEFAULT_MEDIA_URL = "https://media.hypurr.fun";
208
+ var IGNORED_EXTERNAL_SIGNATURE = "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b";
190
209
  var DEFAULT_TELEGRAM_SCOPES = [
191
210
  "telegram:user:read",
192
211
  "telegram:wallet:read",
@@ -195,14 +214,69 @@ var DEFAULT_TELEGRAM_SCOPES = [
195
214
  "telegram:trade:write",
196
215
  "telegram:cabal:read",
197
216
  "telegram:cabal:write",
198
- "telegram:agent:write"
217
+ "telegram:agent:write",
218
+ "telegram:support:read",
219
+ "telegram:support:write"
199
220
  ];
221
+ function createExternalSigningWallet(address) {
222
+ return {
223
+ address,
224
+ signTypedData(params) {
225
+ void params;
226
+ return Promise.resolve(IGNORED_EXTERNAL_SIGNATURE);
227
+ }
228
+ };
229
+ }
200
230
  function isInvalidTelegramAuthError(err) {
201
231
  const msg = err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : String(err);
202
232
  return /invalid telegram auth data|invalid auth token|missing authorization token/i.test(
203
233
  msg
204
234
  );
205
235
  }
236
+ function normalizeMediaUrl(mediaUrl) {
237
+ return (mediaUrl?.trim() || DEFAULT_MEDIA_URL).replace(/\/+$/, "");
238
+ }
239
+ function isAddress(value) {
240
+ return !!value && /^0x[a-fA-F0-9]{40}$/.test(value);
241
+ }
242
+ function getRawSignedTransaction(result) {
243
+ if (typeof result === "string" && result.startsWith("0x")) {
244
+ return result;
245
+ }
246
+ if (!result || typeof result !== "object") return null;
247
+ const record = result;
248
+ const candidates = [
249
+ record.raw,
250
+ record.rawTransaction,
251
+ record.signedTransaction,
252
+ record.serializedTransaction,
253
+ record.result
254
+ ];
255
+ const raw = candidates.find(
256
+ (value) => typeof value === "string" && value.startsWith("0x")
257
+ );
258
+ return raw ? raw : null;
259
+ }
260
+ function decodeJsonBytes(bytes) {
261
+ const text = new TextDecoder().decode(bytes);
262
+ if (!text) return null;
263
+ try {
264
+ return JSON.parse(text);
265
+ } catch {
266
+ return text;
267
+ }
268
+ }
269
+ function encodeJsonBytes(value) {
270
+ return new TextEncoder().encode(JSON.stringify(value));
271
+ }
272
+ function withExpectedFrom(transaction, address) {
273
+ if (transaction.from && transaction.from.toLowerCase() !== address.toLowerCase()) {
274
+ throw new Error(
275
+ "[HypurrConnect] EVM transaction `from` does not match the connected wallet."
276
+ );
277
+ }
278
+ return transaction.from ? transaction : { ...transaction, from: address };
279
+ }
206
280
  function currentReturnTo() {
207
281
  const url = new URL(window.location.href);
208
282
  for (const param of [
@@ -465,10 +539,12 @@ function HypurrConnectProvider({
465
539
  ]);
466
540
  const user = useMemo(() => {
467
541
  if (tgAuthToken && authMethod === "telegram" && selectedWallet && tgUser) {
542
+ const mediaUrl = normalizeMediaUrl(config.mediaUrl);
468
543
  return {
469
544
  address: selectedWallet.ethereumAddress,
470
545
  walletId: selectedWallet.id,
471
546
  displayName: tgUser.telegramUsername ? `@${tgUser.telegramUsername}` : `Telegram ${tgUser.telegramId}`,
547
+ photoUrl: tgUser.pictureFileId ? `${mediaUrl}/${tgUser.pictureFileId}` : void 0,
472
548
  authMethod: "telegram",
473
549
  telegramId: String(tgUser.telegramId),
474
550
  hfunScore: tgUser?.reputation?.hfunScore,
@@ -484,7 +560,14 @@ function HypurrConnectProvider({
484
560
  };
485
561
  }
486
562
  return null;
487
- }, [tgAuthToken, selectedWallet, eoaAddress, authMethod, tgUser]);
563
+ }, [
564
+ tgAuthToken,
565
+ selectedWallet,
566
+ eoaAddress,
567
+ authMethod,
568
+ tgUser,
569
+ config.mediaUrl
570
+ ]);
488
571
  const onDeadAgentRef = useRef(
489
572
  null
490
573
  );
@@ -512,8 +595,7 @@ function HypurrConnectProvider({
512
595
  });
513
596
  return new ExchangeClient({
514
597
  transport,
515
- externalSigning: true,
516
- userAddress: user.address
598
+ wallet: createExternalSigningWallet(user.address)
517
599
  });
518
600
  }
519
601
  if (authMethod === "eoa" && eoaAddress) {
@@ -529,8 +611,7 @@ function HypurrConnectProvider({
529
611
  };
530
612
  return new ExchangeClient({
531
613
  transport: noAgentTransport,
532
- externalSigning: true,
533
- userAddress: eoaAddress
614
+ wallet: createExternalSigningWallet(eoaAddress)
534
615
  });
535
616
  }
536
617
  const isTestnet = config.isTestnet ?? false;
@@ -671,6 +752,81 @@ function HypurrConnectProvider({
671
752
  setAgent(null);
672
753
  }
673
754
  }, [eoaAddress]);
755
+ const signEvmTransaction = useCallback(
756
+ async (transaction) => {
757
+ if (authMethod === "eoa") {
758
+ if (!eoaAddress) {
759
+ throw new Error("[HypurrConnect] No EOA wallet connected.");
760
+ }
761
+ const signer = eoaSignerRef.current;
762
+ if (!signer) {
763
+ throw new Error(
764
+ "[HypurrConnect] No EOA signer available. Pass a signer to connectEoa(address, signer)."
765
+ );
766
+ }
767
+ const tx = withExpectedFrom(transaction, eoaAddress);
768
+ const result = signer.signTransaction ? await signer.signTransaction(tx) : signer.request ? await signer.request({
769
+ method: "eth_signTransaction",
770
+ params: [tx]
771
+ }) : null;
772
+ const rawTransaction = getRawSignedTransaction(result);
773
+ if (!rawTransaction) {
774
+ throw new Error(
775
+ "[HypurrConnect] EOA signer did not return a raw transaction."
776
+ );
777
+ }
778
+ return rawTransaction;
779
+ }
780
+ if (authMethod === "telegram") {
781
+ if (!telegramRpcOptions) {
782
+ throw new Error("[HypurrConnect] No Telegram RPC session available.");
783
+ }
784
+ if (!selectedWallet) {
785
+ throw new Error("[HypurrConnect] No Telegram wallet selected.");
786
+ }
787
+ if (selectedWallet.id <= 0 || selectedWallet.isReadOnly || selectedWallet.isAgent) {
788
+ throw new Error(
789
+ "[HypurrConnect] Select a Telegram private-key wallet to sign EVM transactions."
790
+ );
791
+ }
792
+ if (!isAddress(selectedWallet.ethereumAddress)) {
793
+ throw new Error(
794
+ "[HypurrConnect] Selected Telegram wallet does not have a valid EVM address."
795
+ );
796
+ }
797
+ const tx = withExpectedFrom(
798
+ transaction,
799
+ selectedWallet.ethereumAddress
800
+ );
801
+ try {
802
+ const signResponse = await tgClient.eVMSignTransaction(
803
+ {
804
+ authData: {},
805
+ walletId: selectedWallet.id,
806
+ params: encodeJsonBytes(tx)
807
+ },
808
+ telegramRpcOptions
809
+ );
810
+ const rawTransaction = getRawSignedTransaction(
811
+ decodeJsonBytes(signResponse.response.result)
812
+ );
813
+ if (!rawTransaction) {
814
+ throw new Error(
815
+ "[HypurrConnect] Telegram signer did not return a raw transaction."
816
+ );
817
+ }
818
+ return rawTransaction;
819
+ } catch (err) {
820
+ if (isInvalidTelegramAuthError(err)) {
821
+ onInvalidAuthRef.current?.();
822
+ }
823
+ throw err;
824
+ }
825
+ }
826
+ throw new Error("[HypurrConnect] No wallet connected.");
827
+ },
828
+ [authMethod, eoaAddress, selectedWallet, telegramRpcOptions, tgClient]
829
+ );
674
830
  const createWallet = useCallback(
675
831
  async (name) => {
676
832
  const { response } = await tgClient.hyperliquidWalletCreate(
@@ -936,7 +1092,13 @@ function HypurrConnectProvider({
936
1092
  "[HypurrConnect] Cannot approve agent: no EOA wallet connected. Call connectEoa(address) first."
937
1093
  );
938
1094
  }
939
- eoaSignerRef.current = { signTypedData: signTypedDataAsync, chainId };
1095
+ const currentSigner = eoaSignerRef.current;
1096
+ eoaSignerRef.current = {
1097
+ signTypedData: signTypedDataAsync,
1098
+ signTransaction: currentSigner?.signTransaction,
1099
+ request: currentSigner?.request,
1100
+ chainId
1101
+ };
940
1102
  setEoaLoading(true);
941
1103
  setEoaError(null);
942
1104
  try {
@@ -1055,6 +1217,7 @@ function HypurrConnectProvider({
1055
1217
  closeLoginModal,
1056
1218
  loginTelegram,
1057
1219
  connectEoa,
1220
+ signEvmTransaction,
1058
1221
  approveAgent: approveAgentFn,
1059
1222
  logout,
1060
1223
  agent,
@@ -1098,6 +1261,7 @@ function HypurrConnectProvider({
1098
1261
  closeLoginModal,
1099
1262
  loginTelegram,
1100
1263
  connectEoa,
1264
+ signEvmTransaction,
1101
1265
  approveAgentFn,
1102
1266
  logout,
1103
1267
  agent,
@@ -1520,10 +1684,12 @@ function MobileDrawer({
1520
1684
  }
1521
1685
 
1522
1686
  // src/types.ts
1523
- function createEoaSigner(signTypedDataAsync, chainId) {
1687
+ function createEoaSigner(signTypedDataAsync, chainId, options = {}) {
1524
1688
  const resolve = typeof signTypedDataAsync === "function" ? signTypedDataAsync : (args) => signTypedDataAsync.current(args);
1525
1689
  return {
1526
1690
  signTypedData: (params) => resolve(params),
1691
+ signTransaction: options.signTransaction,
1692
+ request: options.request,
1527
1693
  chainId
1528
1694
  };
1529
1695
  }
@@ -1531,6 +1697,7 @@ export {
1531
1697
  GrpcExchangeTransport,
1532
1698
  HypurrConnectProvider,
1533
1699
  LoginModal,
1700
+ PrivateKeySigner,
1534
1701
  createEoaSigner,
1535
1702
  createStaticClient,
1536
1703
  createTelegramClient,