@hfunlabs/hypurr-connect 0.1.14 → 0.1.15

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.
@@ -9,15 +9,24 @@ import {
9
9
  type MouseEvent as ReactMouseEvent,
10
10
  type ReactNode,
11
11
  } from "react";
12
+ import {
13
+ AgentExpiryWarningIcon,
14
+ EXPIRED_AGENT_COLOR,
15
+ } from "./AgentExpiryWarning";
12
16
  import { useHypurrConnectInternal } from "./HypurrConnectProvider";
13
17
  import { UserProfileModal, type SlippageOption } from "./UserProfileModal";
18
+ import { getAgentExpiryTitle } from "./agentWallet";
14
19
  import {
20
+ Bot,
15
21
  Copy,
16
22
  Crown,
23
+ Eye,
17
24
  Folder,
25
+ KeyRound,
18
26
  LayoutDashboard,
19
27
  LogOut,
20
28
  Plus,
29
+ RefreshCw,
21
30
  Star,
22
31
  User,
23
32
  Wallet,
@@ -36,6 +45,8 @@ export interface WalletSelectorDropdownProps {
36
45
  onAddWallet?: () => void;
37
46
  /** Called when the user clicks the portfolio icon on a wallet row. */
38
47
  onShowPortfolio?: (wallet: HyperliquidWallet) => void;
48
+ /** Called when the user clicks an agent wallet renew action. Host should connect the owner wallet and call `renewAgentWallet`. */
49
+ onRenewAgentWallet?: (wallet: HyperliquidWallet) => void;
39
50
  /**
40
51
  * Called before the SDK's `logout()` runs. Host can do extra cleanup
41
52
  * (e.g. wagmi `disconnect()`).
@@ -67,6 +78,21 @@ export interface WalletSelectorDropdownProps {
67
78
  principalColors?: PrincipalColorOverrides;
68
79
  /** CSS color used as the dropdown panel background. Defaults to `rgba(20,20,20,0.95)`. */
69
80
  backgroundColor?: string;
81
+ /** Extra footer rows rendered between "Profile & Settings" and "Logout". */
82
+ extraFooterItems?: ExtraFooterItem[];
83
+ }
84
+
85
+ export interface ExtraFooterItem {
86
+ /** Stable key for React reconciliation. */
87
+ key: string;
88
+ /** Leading icon (e.g. an icon from `./icons/lucide`). */
89
+ icon: ReactNode;
90
+ /** Row label. */
91
+ label: string;
92
+ /** Click handler. The dropdown is NOT auto-closed — call `onClose` yourself if desired. */
93
+ onClick: () => void;
94
+ /** Optional hover text color (matches the "Logout" red-on-hover pattern). */
95
+ hoverColor?: string;
70
96
  }
71
97
 
72
98
  const DEFAULT_BACKGROUND_COLOR = "rgba(20,20,20,0.95)";
@@ -75,6 +101,10 @@ interface DropdownWallet {
75
101
  id: number;
76
102
  name?: string | null;
77
103
  ethereumAddress?: string;
104
+ agentEthereumAddress?: HyperliquidWallet["agentEthereumAddress"];
105
+ isAgent?: boolean;
106
+ isReadOnly?: boolean;
107
+ agentExpiresAt?: HyperliquidWallet["agentExpiresAt"];
78
108
  }
79
109
 
80
110
  interface WalletListItem {
@@ -171,6 +201,41 @@ const formatCompactAddress = (addr: string | undefined) => {
171
201
  return `${addr.slice(0, 4)}...${addr.slice(-2)}`;
172
202
  };
173
203
 
204
+ const getDisplayAddress = (wallet: DropdownWallet) =>
205
+ wallet.isAgent && wallet.agentEthereumAddress?.value
206
+ ? wallet.agentEthereumAddress.value
207
+ : wallet.ethereumAddress;
208
+
209
+ type WalletTypeMeta = {
210
+ label: string;
211
+ color: string;
212
+ icon: ReactNode;
213
+ };
214
+
215
+ function getWalletTypeMeta(wallet: DropdownWallet): WalletTypeMeta {
216
+ if (wallet.isAgent) {
217
+ return {
218
+ label: "Agent wallet",
219
+ color: "#38bdf8",
220
+ icon: <Bot size={12} />,
221
+ };
222
+ }
223
+
224
+ if (wallet.isReadOnly) {
225
+ return {
226
+ label: "Read-only wallet",
227
+ color: "#a78bfa",
228
+ icon: <Eye size={12} />,
229
+ };
230
+ }
231
+
232
+ return {
233
+ label: "Private-key wallet",
234
+ color: "#34d399",
235
+ icon: <KeyRound size={12} />,
236
+ };
237
+ }
238
+
174
239
  const rootStyle: CSSProperties = {
175
240
  position: "absolute",
176
241
  right: 0,
@@ -217,6 +282,7 @@ export function WalletSelectorDropdown({
217
282
  onClose,
218
283
  onAddWallet,
219
284
  onShowPortfolio,
285
+ onRenewAgentWallet,
220
286
  onLogout,
221
287
  onNotify,
222
288
  vipThreshold = 10,
@@ -230,6 +296,7 @@ export function WalletSelectorDropdown({
230
296
  accentColor,
231
297
  principalColors,
232
298
  backgroundColor = DEFAULT_BACKGROUND_COLOR,
299
+ extraFooterItems,
233
300
  }: WalletSelectorDropdownProps): ReactNode {
234
301
  const { user, wallets, selectedWalletId, selectWallet, logout, authMethod } =
235
302
  useHypurrConnectInternal();
@@ -269,13 +336,19 @@ export function WalletSelectorDropdown({
269
336
  const renderWalletRow = (item: WalletListItem, depth: number): ReactNode => {
270
337
  const { wallet, label } = item;
271
338
  const isSelected = wallet.id === selectedWalletId;
272
- const compactAddress = formatCompactAddress(wallet.ethereumAddress);
339
+ const displayAddress = getDisplayAddress(wallet);
340
+ const compactAddress = formatCompactAddress(displayAddress);
341
+ const agentExpiryTitle = getAgentExpiryTitle({
342
+ isAgent: !!wallet.isAgent,
343
+ agentExpiresAt: wallet.agentExpiresAt,
344
+ });
273
345
  return (
274
346
  <WalletRow
275
347
  key={wallet.id}
276
348
  depth={depth}
277
349
  isSelected={isSelected}
278
350
  label={label}
351
+ walletType={getWalletTypeMeta(wallet)}
279
352
  compactAddress={compactAddress}
280
353
  onSelect={() => {
281
354
  selectWallet(wallet.id);
@@ -289,7 +362,16 @@ export function WalletSelectorDropdown({
289
362
  }
290
363
  : undefined
291
364
  }
292
- onCopy={() => handleCopyAddress(wallet.ethereumAddress)}
365
+ onCopy={() => handleCopyAddress(displayAddress)}
366
+ agentExpiryTitle={agentExpiryTitle}
367
+ onRenewAgentWallet={
368
+ wallet.isAgent && onRenewAgentWallet
369
+ ? () => {
370
+ onRenewAgentWallet(wallet as HyperliquidWallet);
371
+ onClose();
372
+ }
373
+ : undefined
374
+ }
293
375
  colors={colors}
294
376
  />
295
377
  );
@@ -557,6 +639,15 @@ export function WalletSelectorDropdown({
557
639
  icon={<User size={14} />}
558
640
  label="Profile & Settings"
559
641
  />
642
+ {extraFooterItems?.map((item) => (
643
+ <FooterBtn
644
+ key={item.key}
645
+ onClick={item.onClick}
646
+ icon={item.icon}
647
+ label={item.label}
648
+ hoverColor={item.hoverColor}
649
+ />
650
+ ))}
560
651
  <FooterBtn
561
652
  onClick={handleLogout}
562
653
  icon={<LogOut size={14} />}
@@ -580,6 +671,7 @@ export function WalletSelectorDropdown({
580
671
  principalColors={principalColors}
581
672
  onWalletDeleted={onWalletDeleted}
582
673
  onWalletRenamed={onWalletRenamed}
674
+ onRenewAgentWallet={onRenewAgentWallet}
583
675
  onShowPortfolio={
584
676
  onShowPortfolio
585
677
  ? (wallet) => {
@@ -628,21 +720,30 @@ function WalletRow({
628
720
  depth,
629
721
  isSelected,
630
722
  label,
723
+ walletType,
631
724
  compactAddress,
632
725
  onSelect,
633
726
  onShowPortfolio,
634
727
  onCopy,
728
+ agentExpiryTitle,
729
+ onRenewAgentWallet,
635
730
  colors,
636
731
  }: {
637
732
  depth: number;
638
733
  isSelected: boolean;
639
734
  label: string | null;
735
+ walletType: WalletTypeMeta;
640
736
  compactAddress: string;
641
737
  onSelect: () => void;
642
738
  onShowPortfolio?: () => void;
643
739
  onCopy: () => void;
740
+ agentExpiryTitle?: string | null;
741
+ onRenewAgentWallet?: () => void;
644
742
  colors: PrincipalColors;
645
743
  }) {
744
+ const isAgentExpired = !!agentExpiryTitle;
745
+ const walletTextColor = isAgentExpired ? EXPIRED_AGENT_COLOR : "#d1d5db";
746
+ const mutedTextColor = isAgentExpired ? "rgba(245,158,11,0.78)" : "#6b7280";
646
747
  return (
647
748
  <div
648
749
  style={{
@@ -652,7 +753,7 @@ function WalletRow({
652
753
  paddingTop: 6,
653
754
  paddingBottom: 6,
654
755
  fontSize: 14,
655
- color: "#d1d5db",
756
+ color: walletTextColor,
656
757
  display: "flex",
657
758
  alignItems: "center",
658
759
  background: isSelected ? "rgba(255,255,255,0.06)" : "transparent",
@@ -689,6 +790,19 @@ function WalletRow({
689
790
  padding: 0,
690
791
  }}
691
792
  >
793
+ <span
794
+ title={walletType.label}
795
+ aria-label={walletType.label}
796
+ style={{
797
+ display: "inline-flex",
798
+ alignItems: "center",
799
+ justifyContent: "center",
800
+ flexShrink: 0,
801
+ color: isAgentExpired ? walletTextColor : walletType.color,
802
+ }}
803
+ >
804
+ {walletType.icon}
805
+ </span>
692
806
  {label ? (
693
807
  <>
694
808
  <span
@@ -696,7 +810,11 @@ function WalletRow({
696
810
  overflow: "hidden",
697
811
  textOverflow: "ellipsis",
698
812
  whiteSpace: "nowrap",
699
- color: isSelected ? "#fff" : undefined,
813
+ color: isAgentExpired
814
+ ? walletTextColor
815
+ : isSelected
816
+ ? "#fff"
817
+ : undefined,
700
818
  fontWeight: isSelected ? 500 : undefined,
701
819
  }}
702
820
  >
@@ -707,7 +825,7 @@ function WalletRow({
707
825
  fontSize: 12,
708
826
  fontWeight: 400,
709
827
  flexShrink: 0,
710
- color: "#6b7280",
828
+ color: mutedTextColor,
711
829
  }}
712
830
  >
713
831
  ({compactAddress})
@@ -717,7 +835,11 @@ function WalletRow({
717
835
  <span
718
836
  style={{
719
837
  fontSize: 12,
720
- color: isSelected ? "#fff" : "#9ca3af",
838
+ color: isAgentExpired
839
+ ? walletTextColor
840
+ : isSelected
841
+ ? "#fff"
842
+ : "#9ca3af",
721
843
  fontWeight: isSelected ? 500 : undefined,
722
844
  }}
723
845
  >
@@ -725,6 +847,12 @@ function WalletRow({
725
847
  </span>
726
848
  )}
727
849
  </button>
850
+ {agentExpiryTitle && (
851
+ <AgentExpiryWarningIcon
852
+ message={agentExpiryTitle}
853
+ onClick={onRenewAgentWallet}
854
+ />
855
+ )}
728
856
  <div
729
857
  data-row-actions
730
858
  style={{
@@ -734,6 +862,18 @@ function WalletRow({
734
862
  transition: "opacity 120ms",
735
863
  }}
736
864
  >
865
+ {onRenewAgentWallet && (
866
+ <RowIconBtn
867
+ title="Renew agent wallet"
868
+ hoverColor={isAgentExpired ? EXPIRED_AGENT_COLOR : colors.accent}
869
+ onClick={(e) => {
870
+ e.stopPropagation();
871
+ onRenewAgentWallet();
872
+ }}
873
+ >
874
+ <RefreshCw size={12} />
875
+ </RowIconBtn>
876
+ )}
737
877
  {onShowPortfolio && (
738
878
  <RowIconBtn
739
879
  title="View portfolio"
package/src/agent.ts CHANGED
@@ -50,14 +50,14 @@ interface ExtraAgent {
50
50
  validUntil: number;
51
51
  }
52
52
 
53
- /**
54
- * Query the Hyperliquid info API for the named agents registered to a user.
55
- * Returns the matching entry for AGENT_NAME if it exists and is still valid.
56
- */
57
- export async function fetchActiveAgent(
53
+ function isNamedAgent(agent: ExtraAgent): boolean {
54
+ return agent.name.replace(/ valid_until \d+$/, "") === AGENT_NAME;
55
+ }
56
+
57
+ async function fetchExtraAgents(
58
58
  userAddress: string,
59
59
  isTestnet: boolean,
60
- ): Promise<ExtraAgent | null> {
60
+ ): Promise<ExtraAgent[]> {
61
61
  const url = isTestnet
62
62
  ? "https://api.hyperliquid-testnet.xyz/info"
63
63
  : "https://api.hyperliquid.xyz/info";
@@ -68,17 +68,43 @@ export async function fetchActiveAgent(
68
68
  body: JSON.stringify({ type: "extraAgents", user: userAddress }),
69
69
  });
70
70
 
71
- if (!res.ok) return null;
71
+ if (!res.ok) return [];
72
72
 
73
73
  const agents: unknown = await res.json();
74
- if (!Array.isArray(agents)) return null;
74
+ if (!Array.isArray(agents)) return [];
75
+
76
+ return (agents as ExtraAgent[]).map((agent) => ({
77
+ ...agent,
78
+ validUntil: agent.validUntil * 1000,
79
+ }));
80
+ }
75
81
 
82
+ /**
83
+ * Query the Hyperliquid info API for the named agents registered to a user.
84
+ * Returns the matching entry for AGENT_NAME if it exists and is still valid.
85
+ */
86
+ export async function fetchActiveAgent(
87
+ userAddress: string,
88
+ isTestnet: boolean,
89
+ ): Promise<ExtraAgent | null> {
76
90
  const nowMs = Date.now();
77
- const match = (agents as ExtraAgent[]).find(
78
- (a) => a.name === AGENT_NAME && a.validUntil * 1000 > nowMs,
79
- );
91
+ const agents = await fetchExtraAgents(userAddress, isTestnet);
92
+ const match = agents.find((a) => isNamedAgent(a) && a.validUntil > nowMs);
80
93
  if (!match) return null;
81
- return { ...match, validUntil: match.validUntil * 1000 };
94
+ return match;
95
+ }
96
+
97
+ export async function fetchAgentByAddress(
98
+ userAddress: string,
99
+ agentAddress: string,
100
+ isTestnet: boolean,
101
+ ): Promise<ExtraAgent | null> {
102
+ const agents = await fetchExtraAgents(userAddress, isTestnet);
103
+ return (
104
+ agents.find(
105
+ (agent) => agent.address.toLowerCase() === agentAddress.toLowerCase(),
106
+ ) ?? null
107
+ );
82
108
  }
83
109
 
84
110
  /**
@@ -0,0 +1,86 @@
1
+ import type { HyperliquidWallet } from "hypurr-grpc/ts/hypurr/wallet";
2
+
3
+ const MS_PER_SECOND = 1_000;
4
+ const NS_PER_MS = 1_000_000;
5
+ const MS_PER_DAY = 24 * 60 * 60 * 1_000;
6
+ const TELEGRAM_AGENT_APPROVAL_NAME = "xyz";
7
+
8
+ export interface AgentApprovalDurationOption {
9
+ label: string;
10
+ durationMs: number;
11
+ }
12
+
13
+ export const DEFAULT_AGENT_APPROVAL_DURATION_MS = MS_PER_DAY;
14
+
15
+ export const AGENT_APPROVAL_DURATION_OPTIONS: AgentApprovalDurationOption[] = [
16
+ { label: "1 day", durationMs: MS_PER_DAY },
17
+ { label: "7 days", durationMs: 7 * MS_PER_DAY },
18
+ { label: "30 days", durationMs: 30 * MS_PER_DAY },
19
+ { label: "90 days", durationMs: 90 * MS_PER_DAY },
20
+ ];
21
+
22
+ type TimestampLike =
23
+ | {
24
+ seconds?: number | string | bigint;
25
+ nanos?: number | string | bigint;
26
+ }
27
+ | string
28
+ | number
29
+ | Date;
30
+
31
+ function toFiniteNumber(value: number | string | bigint | undefined): number {
32
+ if (value === undefined) return 0;
33
+ const numeric = typeof value === "bigint" ? Number(value) : Number(value);
34
+ return Number.isFinite(numeric) ? numeric : 0;
35
+ }
36
+
37
+ export function normalizeAgentApprovalDurationMs(durationMs?: number): number {
38
+ return typeof durationMs === "number" &&
39
+ Number.isFinite(durationMs) &&
40
+ durationMs > 0
41
+ ? durationMs
42
+ : DEFAULT_AGENT_APPROVAL_DURATION_MS;
43
+ }
44
+
45
+ export function createTelegramAgentApprovalName(durationMs?: number): string {
46
+ const expiresAt = Date.now() + normalizeAgentApprovalDurationMs(durationMs);
47
+ return `${TELEGRAM_AGENT_APPROVAL_NAME} valid_until ${expiresAt}`;
48
+ }
49
+
50
+ type WalletWithAgentExpiry = Pick<HyperliquidWallet, "isAgent"> & {
51
+ agentExpiresAt?: unknown;
52
+ };
53
+
54
+ export function agentExpiresAtMs(wallet: WalletWithAgentExpiry): number | null {
55
+ if (!wallet.isAgent || !wallet.agentExpiresAt) return null;
56
+
57
+ const timestamp = wallet.agentExpiresAt as TimestampLike;
58
+ if (timestamp instanceof Date) return timestamp.getTime();
59
+ if (typeof timestamp === "number") return timestamp;
60
+ if (typeof timestamp === "string") {
61
+ const parsed = Date.parse(timestamp);
62
+ return Number.isFinite(parsed) ? parsed : null;
63
+ }
64
+
65
+ const seconds = toFiniteNumber(timestamp.seconds);
66
+ const nanos = toFiniteNumber(timestamp.nanos);
67
+ const ms = seconds * MS_PER_SECOND + Math.floor(nanos / NS_PER_MS);
68
+ return Number.isFinite(ms) && ms > 0 ? ms : null;
69
+ }
70
+
71
+ export function formatAgentExpiry(expiresAtMs: number): string {
72
+ return new Intl.DateTimeFormat(undefined, {
73
+ dateStyle: "medium",
74
+ timeStyle: "short",
75
+ }).format(new Date(expiresAtMs));
76
+ }
77
+
78
+ export function getAgentExpiryTitle(
79
+ wallet: WalletWithAgentExpiry,
80
+ ): string | null {
81
+ const expiresAtMs = agentExpiresAtMs(wallet);
82
+ if (!expiresAtMs || expiresAtMs > Date.now()) return null;
83
+
84
+ const expiresAt = formatAgentExpiry(expiresAtMs);
85
+ return `Agent approval expired ${expiresAt}. Connect the owner wallet to renew this agent.`;
86
+ }
package/src/css.d.ts ADDED
@@ -0,0 +1 @@
1
+ declare module "*.css";
@@ -70,6 +70,67 @@ export function Wallet(props: IconProps) {
70
70
  );
71
71
  }
72
72
 
73
+ export function Bot(props: IconProps) {
74
+ return (
75
+ <svg {...svgBase(props)}>
76
+ <path d="M12 8V4H8" />
77
+ <rect width="16" height="12" x="4" y="8" rx="2" />
78
+ <path d="M2 14h2" />
79
+ <path d="M20 14h2" />
80
+ <path d="M15 13v2" />
81
+ <path d="M9 13v2" />
82
+ </svg>
83
+ );
84
+ }
85
+
86
+ export function Eye(props: IconProps) {
87
+ return (
88
+ <svg {...svgBase(props)}>
89
+ <path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" />
90
+ <circle cx="12" cy="12" r="3" />
91
+ </svg>
92
+ );
93
+ }
94
+
95
+ export function EyeOff(props: IconProps) {
96
+ return (
97
+ <svg {...svgBase(props)}>
98
+ <path d="M10.733 5.076a10.74 10.74 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.8 10.8 0 0 1-1.444 2.49" />
99
+ <path d="M14.084 14.158a3 3 0 0 1-4.242-4.242" />
100
+ <path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" />
101
+ <path d="m2 2 20 20" />
102
+ </svg>
103
+ );
104
+ }
105
+
106
+ export function Check(props: IconProps) {
107
+ return (
108
+ <svg {...svgBase(props)}>
109
+ <path d="M20 6 9 17l-5-5" />
110
+ </svg>
111
+ );
112
+ }
113
+
114
+ export function KeyRound(props: IconProps) {
115
+ return (
116
+ <svg {...svgBase(props)}>
117
+ <path d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z" />
118
+ <circle cx="16.5" cy="7.5" r=".5" fill="currentColor" />
119
+ </svg>
120
+ );
121
+ }
122
+
123
+ export function RefreshCw(props: IconProps) {
124
+ return (
125
+ <svg {...svgBase(props)}>
126
+ <path d="M3 12a9 9 0 0 1 15.18-6.49" />
127
+ <path d="M21 3v6h-6" />
128
+ <path d="M21 12a9 9 0 0 1-15.18 6.49" />
129
+ <path d="M3 21v-6h6" />
130
+ </svg>
131
+ );
132
+ }
133
+
73
134
  export function TrendingUp(props: IconProps) {
74
135
  return (
75
136
  <svg {...svgBase(props)}>
package/src/index.ts CHANGED
@@ -1,11 +1,20 @@
1
+ import "./styles.css";
2
+
1
3
  export {
2
4
  HypurrConnectProvider,
3
5
  useHypurrConnect,
4
6
  } from "./HypurrConnectProvider";
5
7
  export { LoginModal } from "./LoginModal";
6
8
  export type { LoginModalProps } from "./LoginModal";
9
+ export { AddWalletModal } from "./AddWalletModal";
10
+ export type { AddWalletModalProps } from "./AddWalletModal";
11
+ export { RenewAgentModal } from "./RenewAgentModal";
12
+ export type { RenewAgentModalProps } from "./RenewAgentModal";
7
13
  export { WalletSelectorDropdown } from "./WalletSelectorDropdown";
8
- export type { WalletSelectorDropdownProps } from "./WalletSelectorDropdown";
14
+ export type {
15
+ WalletSelectorDropdownProps,
16
+ ExtraFooterItem,
17
+ } from "./WalletSelectorDropdown";
9
18
  export { UserProfileModal } from "./UserProfileModal";
10
19
  export type { UserProfileModalProps, SlippageOption } from "./UserProfileModal";
11
20
  export { DeleteWalletModal } from "./DeleteWalletModal";
@@ -17,6 +26,12 @@ export { GrpcExchangeTransport } from "./GrpcExchangeTransport";
17
26
  export type { GrpcExchangeTransportConfig } from "./GrpcExchangeTransport";
18
27
  export { createTelegramClient, createStaticClient } from "./grpc";
19
28
  export { PrivateKeySigner } from "./privateKeySigner";
29
+ export {
30
+ AGENT_APPROVAL_DURATION_OPTIONS,
31
+ DEFAULT_AGENT_APPROVAL_DURATION_MS,
32
+ createTelegramAgentApprovalName,
33
+ } from "./agentWallet";
34
+ export type { AgentApprovalDurationOption } from "./agentWallet";
20
35
  export { createEoaSigner } from "./types";
21
36
  export type {
22
37
  AuthMethod,
@@ -29,6 +44,7 @@ export type {
29
44
  HypurrConnectConfig,
30
45
  HypurrConnectState,
31
46
  HypurrUser,
47
+ RenewAgentWalletParams,
32
48
  ScaleCreateParams,
33
49
  SignEvmTransactionFn,
34
50
  SignTypedDataFn,
@@ -37,6 +37,64 @@ export interface PrincipalColors {
37
37
 
38
38
  export type PrincipalColorOverrides = Partial<PrincipalColors>;
39
39
 
40
+ const parseSrgb = (color: string): [number, number, number] | null => {
41
+ const value = color.trim();
42
+ const shortHex = /^#([0-9a-f]{3})$/i.exec(value);
43
+ if (shortHex) {
44
+ const [r, g, b] = shortHex[1].split("").map((c) => parseInt(c + c, 16)) as [
45
+ number,
46
+ number,
47
+ number,
48
+ ];
49
+ return [r, g, b];
50
+ }
51
+ const longHex = /^#([0-9a-f]{6})$/i.exec(value);
52
+ if (longHex) {
53
+ const v = longHex[1];
54
+ return [
55
+ parseInt(v.slice(0, 2), 16),
56
+ parseInt(v.slice(2, 4), 16),
57
+ parseInt(v.slice(4, 6), 16),
58
+ ];
59
+ }
60
+ const rgb =
61
+ /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*[\d.]+\s*)?\)$/i.exec(
62
+ value,
63
+ );
64
+ if (rgb) {
65
+ return [parseInt(rgb[1], 10), parseInt(rgb[2], 10), parseInt(rgb[3], 10)];
66
+ }
67
+ return null;
68
+ };
69
+
70
+ /**
71
+ * WCAG-style relative luminance in [0, 1]. Returns `null` for color formats
72
+ * the parser doesn't understand (named colors, hsl, color-mix, …).
73
+ */
74
+ export const relativeLuminance = (color: string): number | null => {
75
+ const rgb = parseSrgb(color);
76
+ if (!rgb) return null;
77
+ const [r, g, b] = rgb.map((c) => {
78
+ const v = c / 255;
79
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
80
+ }) as [number, number, number];
81
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
82
+ };
83
+
84
+ /**
85
+ * Picks a readable foreground for `background`. Falls back to `lightFg` when
86
+ * the background can't be parsed.
87
+ */
88
+ export const pickContrastColor = (
89
+ background: string,
90
+ lightFg: string = profileColors.text,
91
+ darkFg: string = "#0a0a0a",
92
+ ): string => {
93
+ const lum = relativeLuminance(background);
94
+ if (lum === null) return lightFg;
95
+ return lum > 0.5 ? darkFg : lightFg;
96
+ };
97
+
40
98
  const colorWithAlpha = (color: string, alpha: number): string => {
41
99
  const hex = color.trim();
42
100
  const shortHex = /^#([0-9a-f]{3})$/i.exec(hex);
package/src/styles.css ADDED
@@ -0,0 +1 @@
1
+ .hypurr-connect .btn-raised{background-color:#0d1219;background-image:linear-gradient(180deg,hsla(0,0%,100%,.03),transparent);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1),inset 0 -1px 0 rgba(0,0,0,.35),inset 0 0 0 1px #262a30;color:#aab1c1;transition:background-color .15s,color .15s,background-image .15s,box-shadow .15s}.hypurr-connect .btn-raised:hover:not(:disabled){background-color:#15171a;background-image:linear-gradient(180deg,hsla(0,0%,100%,.04),transparent);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.14),inset 0 -1px 0 rgba(0,0,0,.4),inset 0 0 0 1px #444548;color:#d1d5db}.hypurr-connect .btn-raised-active{background-color:#1e2124;background-image:linear-gradient(180deg,hsla(0,0%,100%,.05),transparent);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.18),inset 0 -1px 0 rgba(0,0,0,.45),inset 0 0 0 1px #4b4d50,0 1px 2px rgba(0,0,0,.5);color:#fff}.hypurr-connect .btn-raised-active,.hypurr-connect .btn-raised-disabled{transition:background-color .15s,color .15s,background-image .15s,box-shadow .15s}.hypurr-connect .btn-raised-disabled{background-color:#0d1219;background-image:linear-gradient(180deg,hsla(0,0%,100%,.02),transparent);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.05),inset 0 -1px 0 rgba(0,0,0,.25),inset 0 0 0 1px #1c2026;color:#7d8597;cursor:not-allowed}.hypurr-connect .\!visible{visibility:visible!important}.hypurr-connect .visible{visibility:visible}.hypurr-connect .fixed{position:fixed}.hypurr-connect .absolute{position:absolute}.hypurr-connect .relative{position:relative}.hypurr-connect .inset-0{inset:0}.hypurr-connect .right-2{right:.5rem}.hypurr-connect .right-6{right:1.5rem}.hypurr-connect .top-1\/2{top:50%}.hypurr-connect .z-\[100\]{z-index:100}.hypurr-connect .z-\[101\]{z-index:101}.hypurr-connect .z-\[110\]{z-index:110}.hypurr-connect .z-\[111\]{z-index:111}.hypurr-connect .mb-1\.5{margin-bottom:.375rem}.hypurr-connect .mb-2{margin-bottom:.5rem}.hypurr-connect .mt-0\.5{margin-top:.125rem}.hypurr-connect .mt-1{margin-top:.25rem}.hypurr-connect .mt-1\.5{margin-top:.375rem}.hypurr-connect .block{display:block}.hypurr-connect .inline-block{display:inline-block}.hypurr-connect .inline{display:inline}.hypurr-connect .flex{display:flex}.hypurr-connect .inline-flex{display:inline-flex}.hypurr-connect .grid{display:grid}.hypurr-connect .contents{display:contents}.hypurr-connect .hidden{display:none}.hypurr-connect .h-8{height:2rem}.hypurr-connect .w-8{width:2rem}.hypurr-connect .w-full{width:100%}.hypurr-connect .min-w-0{min-width:0}.hypurr-connect .max-w-md{max-width:28rem}.hypurr-connect .flex-1{flex:1 1 0%}.hypurr-connect .flex-shrink-0{flex-shrink:0}.hypurr-connect .-translate-y-1\/2{--tw-translate-y:-50%}.hypurr-connect .-translate-y-1\/2,.hypurr-connect .transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hypurr-connect .cursor-not-allowed{cursor:not-allowed}.hypurr-connect .grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.hypurr-connect .items-start{align-items:flex-start}.hypurr-connect .items-center{align-items:center}.hypurr-connect .justify-center{justify-content:center}.hypurr-connect .justify-between{justify-content:space-between}.hypurr-connect .gap-1\.5{gap:.375rem}.hypurr-connect .gap-2{gap:.5rem}.hypurr-connect .gap-3{gap:.75rem}.hypurr-connect :is(.space-y-2>:not([hidden])~:not([hidden])){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.hypurr-connect :is(.space-y-3>:not([hidden])~:not([hidden])){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.hypurr-connect :is(.space-y-4>:not([hidden])~:not([hidden])){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.hypurr-connect .overflow-hidden{overflow:hidden}.hypurr-connect .truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.hypurr-connect .break-all{word-break:break-all}.hypurr-connect .rounded{border-radius:.25rem}.hypurr-connect .rounded-lg{border-radius:.5rem}.hypurr-connect .rounded-md{border-radius:.375rem}.hypurr-connect .border{border-width:1px}.hypurr-connect .border-b{border-bottom-width:1px}.hypurr-connect .border-amber-500\/25{border-color:rgba(245,158,11,.25)}.hypurr-connect .border-gray-700{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity,1))}.hypurr-connect .border-purple-500{--tw-border-opacity:1;border-color:rgb(185 125 241/var(--tw-border-opacity,1))}.hypurr-connect .border-surface-bd{--tw-border-opacity:1;border-color:rgb(38 42 48/var(--tw-border-opacity,1))}.hypurr-connect .border-trade-down\/20{border-color:hsla(0,91%,71%,.2)}.hypurr-connect .border-trade-down\/50{border-color:hsla(0,91%,71%,.5)}.hypurr-connect .border-white\/\[0\.06\]{border-color:hsla(0,0%,100%,.06)}.hypurr-connect .border-white\/\[0\.08\]{border-color:hsla(0,0%,100%,.08)}.hypurr-connect .bg-amber-500\/\[0\.08\]{background-color:rgba(245,158,11,.08)}.hypurr-connect .bg-black\/70{background-color:rgba(0,0,0,.7)}.hypurr-connect .bg-surface-btn\/90{background-color:rgba(13,18,25,.9)}.hypurr-connect .bg-surface-modal{--tw-bg-opacity:1;background-color:rgb(14 18 24/var(--tw-bg-opacity,1))}.hypurr-connect .bg-trade-down\/\[0\.08\]{background-color:hsla(0,91%,71%,.08)}.hypurr-connect .bg-transparent{background-color:transparent}.hypurr-connect .bg-white\/\[0\.03\]{background-color:hsla(0,0%,100%,.03)}.hypurr-connect .bg-white\/\[0\.06\]{background-color:hsla(0,0%,100%,.06)}.hypurr-connect .p-1\.5{padding:.375rem}.hypurr-connect .p-3{padding:.75rem}.hypurr-connect .p-4{padding:1rem}.hypurr-connect .px-3{padding-left:.75rem;padding-right:.75rem}.hypurr-connect .px-6{padding-left:1.5rem;padding-right:1.5rem}.hypurr-connect .py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.hypurr-connect .py-2{padding-top:.5rem;padding-bottom:.5rem}.hypurr-connect .py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.hypurr-connect .py-5{padding-top:1.25rem}.hypurr-connect .pb-5,.hypurr-connect .py-5{padding-bottom:1.25rem}.hypurr-connect .pb-6{padding-bottom:1.5rem}.hypurr-connect .pr-11{padding-right:2.75rem}.hypurr-connect .pt-5{padding-top:1.25rem}.hypurr-connect .pt-6{padding-top:1.5rem}.hypurr-connect .font-mono{font-family:Google Sans Code,Roboto Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}.hypurr-connect .font-sans{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.hypurr-connect .text-base{font-size:12.5px;line-height:1rem}.hypurr-connect .text-lg{font-size:14px;line-height:1.25rem}.hypurr-connect .font-medium{font-weight:500}.hypurr-connect .font-semibold{font-weight:600}.hypurr-connect .uppercase{text-transform:uppercase}.hypurr-connect .tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.hypurr-connect .tracking-\[0\.1em\]{letter-spacing:.1em}.hypurr-connect .text-amber-200{--tw-text-opacity:1;color:rgb(253 230 138/var(--tw-text-opacity,1))}.hypurr-connect .text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.hypurr-connect .text-gray-400{--tw-text-opacity:1;color:rgb(170 177 193/var(--tw-text-opacity,1))}.hypurr-connect .text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.hypurr-connect .text-gray-600{--tw-text-opacity:1;color:rgb(125 133 151/var(--tw-text-opacity,1))}.hypurr-connect .text-purple-400{--tw-text-opacity:1;color:rgb(216 180 254/var(--tw-text-opacity,1))}.hypurr-connect .text-trade-down{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.hypurr-connect .text-trade-up{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.hypurr-connect .text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.hypurr-connect .placeholder-gray-600::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(125 133 151/var(--tw-placeholder-opacity,1))}.hypurr-connect .placeholder-gray-600::placeholder{--tw-placeholder-opacity:1;color:rgb(125 133 151/var(--tw-placeholder-opacity,1))}.hypurr-connect .shadow-modal{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.6);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.hypurr-connect .outline{outline-style:solid}.hypurr-connect .blur{--tw-blur:blur(8px)}.hypurr-connect .blur,.hypurr-connect .drop-shadow{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.hypurr-connect .drop-shadow{--tw-drop-shadow:drop-shadow(0 1px 2px rgba(0,0,0,.1)) drop-shadow(0 1px 1px rgba(0,0,0,.06))}.hypurr-connect .filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.hypurr-connect .backdrop-blur-sm{--tw-backdrop-blur:blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.hypurr-connect .transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.hypurr-connect .transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.hypurr-connect .hover\:bg-purple-500\/10:hover{background-color:rgba(185,125,241,.1)}.hypurr-connect .hover\:text-gray-300:hover{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.hypurr-connect .hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.hypurr-connect .focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.hypurr-connect .disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.hypurr-connect .disabled\:opacity-40:disabled{opacity:.4}.hypurr-connect .disabled\:opacity-50:disabled{opacity:.5}