@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.
- package/README.md +27 -2
- package/dist/index.d.ts +174 -10
- package/dist/index.js +2732 -23
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/src/DeleteWalletModal.tsx +344 -0
- package/src/HypurrConnectProvider.tsx +205 -41
- package/src/RenameWalletModal.tsx +325 -0
- package/src/UserProfileModal.tsx +982 -0
- package/src/WalletSelectorDropdown.tsx +797 -0
- package/src/agent.ts +2 -2
- package/src/icons/lucide.tsx +197 -0
- package/src/index.ts +16 -0
- package/src/privateKeySigner.ts +32 -0
- package/src/profileStyles.ts +213 -0
- package/src/types.ts +48 -10
- package/src/TelegramLoginWidget.tsx +0 -62
|
@@ -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:
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1027
|
-
config.telegram
|
|
1028
|
-
config.telegram
|
|
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
|
|
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,
|