@hfunlabs/hypurr-connect 0.1.11 → 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
@@ -220,6 +220,24 @@ connectEoa(address); // no signer — only L1 actions after manual approval
220
220
  await approveAgent(signTypedDataAsync, chainId);
221
221
  ```
222
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
+
223
241
  ### Using the Exchange Client
224
242
 
225
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:
@@ -381,6 +399,7 @@ Returns the full auth and exchange state. Throws if used outside `HypurrConnectP
381
399
  | `openLoginModal` | `() => void` | Show the login modal |
382
400
  | `closeLoginModal` | `() => void` | Hide the login modal |
383
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 |
384
403
  | `approveAgent` | `(signTypedDataAsync: SignTypedDataFn, chainId: number) => Promise<void>` | Approve a named agent key (async, triggers wallet prompt) |
385
404
  | `logout` | `() => void` | Clear all auth state and localStorage |
386
405
  | `agent` | `StoredAgent \| null` | Current agent key (EOA flow only) |
@@ -473,11 +492,13 @@ Encapsulates the master wallet's EIP-712 signing function and chain ID. Pass to
473
492
  ```typescript
474
493
  interface EoaSigner {
475
494
  signTypedData: SignTypedDataFn;
495
+ signTransaction?: (transaction: EvmTransactionRequest) => Promise<unknown>;
496
+ request?: (args: { method: string; params?: unknown[] }) => Promise<unknown>;
476
497
  chainId: number;
477
498
  }
478
499
  ```
479
500
 
480
- #### `createEoaSigner(signTypedData, chainId): EoaSigner`
501
+ #### `createEoaSigner(signTypedData, chainId, options?): EoaSigner`
481
502
 
482
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:
483
504
 
@@ -489,6 +510,11 @@ const signer = createEoaSigner(signerRef, chainId);
489
510
 
490
511
  // With a direct function (fine for stable references)
491
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
+ });
492
518
  ```
493
519
 
494
520
  #### `SignTypedDataFn`
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ 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. */
@@ -73,11 +74,41 @@ type SignTypedDataFn = (params: {
73
74
  primaryType: string;
74
75
  message: Record<string, unknown>;
75
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>;
76
99
  /** Wallet signer provided at EOA connect time for user-signed actions. */
77
100
  interface EoaSigner {
78
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;
79
106
  chainId: number;
80
107
  }
108
+ interface EoaSignerOptions {
109
+ signTransaction?: EoaSignTransactionFn;
110
+ request?: EvmRequestFn;
111
+ }
81
112
  /**
82
113
  * Create an {@link EoaSigner} from any EIP-712 signing function.
83
114
  *
@@ -103,7 +134,7 @@ interface EoaSigner {
103
134
  */
104
135
  declare function createEoaSigner(signTypedDataAsync: ((args: Record<string, unknown>) => Promise<`0x${string}`>) | {
105
136
  current: (args: Record<string, unknown>) => Promise<`0x${string}`>;
106
- }, chainId: number): EoaSigner;
137
+ }, chainId: number, options?: EoaSignerOptions): EoaSigner;
107
138
  type TwapCreateParams = Omit<HyperliquidTwapCreateRequest, "authData" | "walletId">;
108
139
  type TwapModifyParams = Omit<HyperliquidTwapModifyRequest, "authData" | "walletId">;
109
140
  type ScaleCreateParams = Omit<HyperliquidScaleCreateRequest, "authData" | "walletId">;
@@ -145,6 +176,7 @@ interface HypurrConnectState {
145
176
  openLoginModal: () => void;
146
177
  closeLoginModal: () => void;
147
178
  connectEoa: (address: `0x${string}`, signer?: EoaSigner) => void;
179
+ signEvmTransaction: SignEvmTransactionFn;
148
180
  approveAgent: (signTypedDataAsync: SignTypedDataFn, chainId: number) => Promise<void>;
149
181
  logout: () => void;
150
182
  agent: StoredAgent | null;
@@ -201,4 +233,17 @@ declare class GrpcExchangeTransport implements IRequestTransport {
201
233
  declare function createTelegramClient(config: HypurrConnectConfig): TelegramClient;
202
234
  declare function createStaticClient(config: HypurrConnectConfig): StaticClient;
203
235
 
204
- 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) {
@@ -188,6 +205,7 @@ var TELEGRAM_AUTH_STATE_KEY = "hypurr-connect-auth-state";
188
205
  var TELEGRAM_AUTH_MESSAGE = "hypurr-connect:telegram-auth";
189
206
  var DEFAULT_AUTH_HUB_URL = "https://auth.hypurr.fun/login";
190
207
  var DEFAULT_MEDIA_URL = "https://media.hypurr.fun";
208
+ var IGNORED_EXTERNAL_SIGNATURE = "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b";
191
209
  var DEFAULT_TELEGRAM_SCOPES = [
192
210
  "telegram:user:read",
193
211
  "telegram:wallet:read",
@@ -196,8 +214,19 @@ var DEFAULT_TELEGRAM_SCOPES = [
196
214
  "telegram:trade:write",
197
215
  "telegram:cabal:read",
198
216
  "telegram:cabal:write",
199
- "telegram:agent:write"
217
+ "telegram:agent:write",
218
+ "telegram:support:read",
219
+ "telegram:support:write"
200
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
+ }
201
230
  function isInvalidTelegramAuthError(err) {
202
231
  const msg = err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : String(err);
203
232
  return /invalid telegram auth data|invalid auth token|missing authorization token/i.test(
@@ -207,6 +236,47 @@ function isInvalidTelegramAuthError(err) {
207
236
  function normalizeMediaUrl(mediaUrl) {
208
237
  return (mediaUrl?.trim() || DEFAULT_MEDIA_URL).replace(/\/+$/, "");
209
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
+ }
210
280
  function currentReturnTo() {
211
281
  const url = new URL(window.location.href);
212
282
  for (const param of [
@@ -525,8 +595,7 @@ function HypurrConnectProvider({
525
595
  });
526
596
  return new ExchangeClient({
527
597
  transport,
528
- externalSigning: true,
529
- userAddress: user.address
598
+ wallet: createExternalSigningWallet(user.address)
530
599
  });
531
600
  }
532
601
  if (authMethod === "eoa" && eoaAddress) {
@@ -542,8 +611,7 @@ function HypurrConnectProvider({
542
611
  };
543
612
  return new ExchangeClient({
544
613
  transport: noAgentTransport,
545
- externalSigning: true,
546
- userAddress: eoaAddress
614
+ wallet: createExternalSigningWallet(eoaAddress)
547
615
  });
548
616
  }
549
617
  const isTestnet = config.isTestnet ?? false;
@@ -684,6 +752,81 @@ function HypurrConnectProvider({
684
752
  setAgent(null);
685
753
  }
686
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
+ );
687
830
  const createWallet = useCallback(
688
831
  async (name) => {
689
832
  const { response } = await tgClient.hyperliquidWalletCreate(
@@ -949,7 +1092,13 @@ function HypurrConnectProvider({
949
1092
  "[HypurrConnect] Cannot approve agent: no EOA wallet connected. Call connectEoa(address) first."
950
1093
  );
951
1094
  }
952
- 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
+ };
953
1102
  setEoaLoading(true);
954
1103
  setEoaError(null);
955
1104
  try {
@@ -1068,6 +1217,7 @@ function HypurrConnectProvider({
1068
1217
  closeLoginModal,
1069
1218
  loginTelegram,
1070
1219
  connectEoa,
1220
+ signEvmTransaction,
1071
1221
  approveAgent: approveAgentFn,
1072
1222
  logout,
1073
1223
  agent,
@@ -1111,6 +1261,7 @@ function HypurrConnectProvider({
1111
1261
  closeLoginModal,
1112
1262
  loginTelegram,
1113
1263
  connectEoa,
1264
+ signEvmTransaction,
1114
1265
  approveAgentFn,
1115
1266
  logout,
1116
1267
  agent,
@@ -1533,10 +1684,12 @@ function MobileDrawer({
1533
1684
  }
1534
1685
 
1535
1686
  // src/types.ts
1536
- function createEoaSigner(signTypedDataAsync, chainId) {
1687
+ function createEoaSigner(signTypedDataAsync, chainId, options = {}) {
1537
1688
  const resolve = typeof signTypedDataAsync === "function" ? signTypedDataAsync : (args) => signTypedDataAsync.current(args);
1538
1689
  return {
1539
1690
  signTypedData: (params) => resolve(params),
1691
+ signTransaction: options.signTransaction,
1692
+ request: options.request,
1540
1693
  chainId
1541
1694
  };
1542
1695
  }
@@ -1544,6 +1697,7 @@ export {
1544
1697
  GrpcExchangeTransport,
1545
1698
  HypurrConnectProvider,
1546
1699
  LoginModal,
1700
+ PrivateKeySigner,
1547
1701
  createEoaSigner,
1548
1702
  createStaticClient,
1549
1703
  createTelegramClient,