Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"@headlessui/react": "^1.7.16",
"@heroicons/react": "v1",
"@hookform/resolvers": "^3.1.1",
"@injective-labs/sdk-ts": "^1.10.45",
"@injective-labs/ts-types": "^1.10.45",
"@injective-labs/wallet-strategy": "^1.10.45",
Comment on lines +25 to +27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

@injective-labs/sdk-ts latest version npm

💡 Result:

Latest npm version: @injectivelabs/sdk-ts v1.16.10 (as of Dec 9, 2025). [1][2]

Install: npm install @injectivelabs/sdk-ts@latest or npm i @injectivelabs/sdk-ts. [1]

Sources:

  • npm package page. [1]
  • InjectiveLabs GitHub / docs (package repo). [2]

🌐 Web query:

@injective-labs/ts-types latest version npm

💡 Result:

The latest published npm version of @injective-labs/ts-types is 1.16.37. [1][2]

Sources:

  • jsDelivr/npm package listing showing version 1.16.37. [1]
  • Socket.dev package page (npm metadata) listing recent versions. [2]

🌐 Web query:

@injective-labs/wallet-strategy latest version npm

💡 Result:

Latest npm version of @injective-labs/wallet-strategy is 1.16.7 (published 2 days ago). [1][2]

Sources:

  • npm package page. [1]
  • Injective docs (wallet strategy). [2]

🌐 Web query:

@injective-labs packages security vulnerabilities CVE 2025

💡 Result:

I searched NVD, GitHub security/advisories, and npm for 2025 CVEs affecting @injective-labs packages and found no CVE entries or vendor advisories tied to Injective Labs packages for 2025.

Sources checked:

  • NVD (National Vulnerability Database) search (no CVE for Injective Labs). [1]
  • InjectiveLabs GitHub repos (injective-ts, sdk-go, others) — no published security advisories/CVEs listed. [2][3]
  • npm package listing for @InjectiveLabs (example: @injectivelabs/networks) — no malware/security advisory on the package page. [4]

If you want, I can:

  • perform a deeper scan (package-by-package) across the InjectiveLabs npm scope and their GitHub repos and return any historical advisories or dependency CVEs; or
  • set up alerts/notify if any new CVE appears for these packages.

References:
[1] NVD — National Vulnerability Database.
[2] GitHub — InjectiveLabs/injective-ts.
[3] GitHub — InjectiveLabs/sdk-go.
[4] npm — @injectivelabs/networks.


Update Injective packages to current versions.

The pinned versions are significantly outdated:

  • @injective-labs/sdk-ts: ^1.10.45 → latest v1.16.10
  • @injective-labs/ts-types: ^1.10.45 → latest v1.16.37
  • @injective-labs/wallet-strategy: ^1.10.45 → latest v1.16.7

While no security vulnerabilities were found for these versions, updating to the latest releases will ensure access to bug fixes, performance improvements, and compatibility enhancements.

"@lens-protocol/sdk-gated": "^1.2.0",
"@lenster/abis": "workspace:*",
"@lenster/data": "workspace:*",
Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/components/Common/Providers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ const queryClient = new QueryClient({
});
const apolloClient = webClient;

import { InjectiveWalletProvider } from '@lib/InjectiveWalletProvider';

const Providers = ({ children }: { children: ReactNode }) => {
return (
<LanguageProvider>
Expand All @@ -65,7 +67,9 @@ const Providers = ({ children }: { children: ReactNode }) => {
<QueryClientProvider client={queryClient}>
<LivepeerConfig client={livepeerClient} theme={getLivepeerTheme}>
<ThemeProvider defaultTheme="light" attribute="class">
<Layout>{children}</Layout>
<InjectiveWalletProvider>
<Layout>{children}</Layout>
</InjectiveWalletProvider>
</ThemeProvider>
</LivepeerConfig>
</QueryClientProvider>
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/components/Profile/FeedType.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ const FeedType: FC<FeedTypeProps> = ({ setFeedType, feedType }) => {
onClick={() => switchTab(ProfileFeedType.Stats)}
/>
)}
<TabButton
name="Injective"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing localization for Injective tab name.

The tab name is hardcoded as "Injective" while all other tabs use the t macro for internationalization. This creates inconsistency with the established localization pattern.

Apply this diff:

-        name="Injective"
+        name={t`Injective`}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
name="Injective"
name={t`Injective`}
🤖 Prompt for AI Agents
In apps/web/src/components/Profile/FeedType.tsx around line 84, the tab label is
hardcoded as "Injective" while other tabs use the t(...) localization macro;
replace the hardcoded string with the same t call pattern used elsewhere (e.g.,
t('profile.tabs.injective') or the existing key convention), and if that
translation key does not exist add it to the appropriate i18n translation files
for all supported locales.

icon={<CollectionIcon className="h-4 w-4" />}
active={feedType === ProfileFeedType.Injective}
type={ProfileFeedType.Injective.toLowerCase()}
onClick={() => switchTab(ProfileFeedType.Injective)}
/>
</div>
<div>{feedType === ProfileFeedType.Media && <MediaFilter />}</div>
</div>
Expand Down
109 changes: 109 additions & 0 deletions apps/web/src/components/Profile/InjectivePortfolio.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { useInjectiveWallet } from '@lib/InjectiveWalletProvider';
import { Card, EmptyState, Spinner, ErrorMessage } from '@lenster/ui';
import { t, Trans } from '@lingui/macro';
import { useEffect, useState } from 'react';
import { IndexerGrpcAccountApi } from '@injective-labs/sdk-ts';
import { INJECTIVE_ENDPOINTS } from '@lenster/data/constants';

// Helper to get bank balances
const fetchBalances = async (address: string) => {
const indexerAccountApi = new IndexerGrpcAccountApi(INJECTIVE_ENDPOINTS.indexerApi);
const accountPortfolio = await indexerAccountApi.fetchAccountPortfolio(address);

return accountPortfolio;
};

interface Coin {
denom: string;
amount: string;
}

interface Portfolio {
bankBalancesList: Coin[];
subaccountsList: string[];
}

const InjectivePortfolio = () => {
const { address } = useInjectiveWallet();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const [portfolio, setPortfolio] = useState<Portfolio | null>(null);

useEffect(() => {
if (!address) return;

const loadBalances = async () => {
setLoading(true);
setError(null);
try {
const data = await fetchBalances(address);
setPortfolio(data);
} catch (err: any) {
setError(new Error(err?.message || 'Failed to fetch balances'));
console.error('Failed to fetch Injective balances', err);
} finally {
setLoading(false);
}
};

loadBalances();
}, [address]);
Comment on lines +32 to +50
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Memory leak risk: missing cleanup in async useEffect.

The useEffect performs an async operation but doesn't handle component unmount. If the component unmounts while loadBalances is in progress, the state updates on lines 40 and 45 will execute on an unmounted component, potentially causing memory leaks and React warnings.

Apply this diff to add cleanup:

 useEffect(() => {
   if (!address) return;
+  
+  let isMounted = true;

   const loadBalances = async () => {
     setLoading(true);
     setError(null);
     try {
       const data = await fetchBalances(address);
-      setPortfolio(data);
+      if (isMounted) {
+        setPortfolio(data);
+      }
     } catch (err: any) {
-      setError(new Error(err?.message || 'Failed to fetch balances'));
-      console.error('Failed to fetch Injective balances', err);
+      if (isMounted) {
+        setError(new Error(err?.message || 'Failed to fetch balances'));
+        console.error('Failed to fetch Injective balances', err);
+      }
     } finally {
-      setLoading(false);
+      if (isMounted) {
+        setLoading(false);
+      }
     }
   };

   loadBalances();
+  
+  return () => {
+    isMounted = false;
+  };
 }, [address]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (!address) return;
const loadBalances = async () => {
setLoading(true);
setError(null);
try {
const data = await fetchBalances(address);
setPortfolio(data);
} catch (err: any) {
setError(new Error(err?.message || 'Failed to fetch balances'));
console.error('Failed to fetch Injective balances', err);
} finally {
setLoading(false);
}
};
loadBalances();
}, [address]);
useEffect(() => {
if (!address) return;
let isMounted = true;
const loadBalances = async () => {
setLoading(true);
setError(null);
try {
const data = await fetchBalances(address);
if (isMounted) {
setPortfolio(data);
}
} catch (err: any) {
if (isMounted) {
setError(new Error(err?.message || 'Failed to fetch balances'));
console.error('Failed to fetch Injective balances', err);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
loadBalances();
return () => {
isMounted = false;
};
}, [address]);
🤖 Prompt for AI Agents
In apps/web/src/components/Profile/InjectivePortfolio.tsx around lines 32 to 50,
the useEffect starts an async loadBalances that updates state but doesn't guard
against component unmount, risking state updates on an unmounted component; fix
by adding a mounted flag (let isMounted = true) or an AbortController if
fetchBalances supports a signal, pass the signal to fetchBalances (or check
isMounted) and before every setLoading, setError, and setPortfolio ensure
isMounted is true (or abort early on signal), and return a cleanup function that
sets isMounted = false (or calls controller.abort()) to prevent state updates
after unmount.


if (!address) {
return (
<EmptyState
message={t`Connect Injective wallet to view assets`}
icon={<div className="text-xl">🏦</div>}
hideCard
/>
);
}

if (loading) {
return (
<Card className="p-5">
<div className="flex justify-center">
<Spinner size="md" />
</div>
</Card>
);
}

if (error) {
return (
<ErrorMessage
title={t`Failed to load Injective portfolio`}
error={error}
/>
);
}

const bankBalances = portfolio?.bankBalancesList || [];
const subaccountBalances = portfolio?.subaccountsList || [];

return (
<div className="space-y-5">
<Card className="p-5">
<div className="text-lg font-bold mb-4">
<Trans>Injective Assets</Trans>
</div>
{bankBalances.length === 0 && subaccountBalances.length === 0 ? (
<div className="text-gray-500">
<Trans>No assets found.</Trans>
</div>
) : (
<div className="space-y-4">
{bankBalances.map((balance: Coin, i: number) => (
<div key={i} className="flex justify-between border-b pb-2 last:border-b-0">
<span className="font-mono">{balance.denom}</span>
<span className="font-semibold">{balance.amount}</span>
</div>
))}
</div>
)}
</Card>
</div>
);
};

export default InjectivePortfolio;
2 changes: 2 additions & 0 deletions apps/web/src/components/Profile/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import Feed from './Feed';
import FeedType from './FeedType';
import FollowDialog from './FollowDialog';
import NftGallery from './NftGallery';
import InjectivePortfolio from './InjectivePortfolio';
import ProfilePageShimmer from './Shimmer';

const ViewProfile: NextPage = () => {
Expand Down Expand Up @@ -157,6 +158,7 @@ const ViewProfile: NextPage = () => {
{feedType === ProfileFeedType.Stats && IS_MAINNET ? (
<Achievements profile={profile as Profile} />
) : null}
{feedType === ProfileFeedType.Injective ? <InjectivePortfolio /> : null}
</GridItemEight>
</GridLayout>
</>
Expand Down
18 changes: 18 additions & 0 deletions apps/web/src/components/Shared/Login/WalletSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import {
useNetwork,
useSignMessage
} from 'wagmi';
import { useInjectiveWallet } from '@lib/InjectiveWalletProvider';
import { Wallet } from '@injective-labs/wallet-strategy';

interface WalletSelectorProps {
setHasConnected: Dispatch<boolean>;
Expand All @@ -46,6 +48,7 @@ const WalletSelector: FC<WalletSelectorProps> = ({
const setShowAuthModal = useGlobalModalStateStore(
(state) => state.setShowAuthModal
);
const { connect: connectInjective } = useInjectiveWallet();
const [isLoading, setIsLoading] = useState(false);

const onError = (error: any) => {
Expand Down Expand Up @@ -225,6 +228,21 @@ const WalletSelector: FC<WalletSelectorProps> = ({
</button>
);
})}
<button
type="button"
className="flex w-full items-center justify-between space-x-2.5 overflow-hidden rounded-xl border px-4 py-3 outline-none hover:bg-gray-100 dark:border-gray-700 dark:hover:bg-gray-700"
onClick={() => connectInjective(Wallet.Keplr)}
>
<span>Keplr (Injective)</span>
<img
src="https://keplr.app/img/keplr.png"
draggable={false}
className="h-6 w-6"
height={24}
width={24}
alt="Keplr"
/>
Comment on lines +237 to +244
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reliability concern: external image URL and missing error handling.

Two issues:

  1. The Keplr logo uses an external URL (https://keplr.app/img/keplr.png) which creates a single point of failure. If the external domain is unavailable or the image path changes, the UI will break.
  2. The connectInjective call lacks error handling. Unlike the standard wallet connectors which have error handling via the useConnect hook (line 61), failures in Injective wallet connection won't be caught or displayed to users.

Recommended fixes:

  1. Host the Keplr logo locally in your static assets directory
  2. Wrap the onClick handler with try-catch to handle connection errors:
-        onClick={() => connectInjective(Wallet.Keplr)}
+        onClick={async () => {
+          try {
+            await connectInjective(Wallet.Keplr);
+          } catch (err: any) {
+            errorToast(err);
+          }
+        }}

Committable suggestion skipped: line range outside the PR's diff.

</button>
{error?.message ? (
<div className="flex items-center space-x-1 text-red-500">
<XCircleIcon className="h-5 w-5" />
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export enum ProfileFeedType {
Media = 'MEDIA',
Collects = 'COLLECTS',
Nft = 'NFT',
Stats = 'STATS'
Stats = 'STATS',
Injective = 'INJECTIVE'
}

export enum MessageTabs {
Expand Down
71 changes: 71 additions & 0 deletions apps/web/src/lib/InjectiveWalletProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { ChainId } from '@injective-labs/ts-types';
import { Wallet, WalletStrategy } from '@injective-labs/wallet-strategy';
import { createContext, useContext, useEffect, useMemo, useState } from 'react';

interface InjectiveWalletContextType {
walletStrategy: WalletStrategy;
address: string;
chainId: ChainId;
connect: (wallet: Wallet) => Promise<void>;
disconnect: () => void;
isLoading: boolean;
}

const InjectiveWalletContext = createContext<InjectiveWalletContextType | null>(null);

export const useInjectiveWallet = () => {
const context = useContext(InjectiveWalletContext);
if (!context) {
throw new Error('useInjectiveWallet must be used within an InjectiveWalletProvider');
}
return context;
};

export const InjectiveWalletProvider = ({ children }: { children: React.ReactNode }) => {
const [address, setAddress] = useState('');
const [isLoading, setIsLoading] = useState(false);

// Initialize Wallet Strategy
// TODO: Make chainId configurable via env
const chainId = (process.env.NEXT_PUBLIC_INJECTIVE_CHAIN_ID as unknown as ChainId) || ChainId.Mainnet;

const walletStrategy = useMemo(() => new WalletStrategy({
chainId,
// Add compatible wallets here
wallet: Wallet.Keplr
}), [chainId]);

const connect = async (wallet: Wallet) => {
setIsLoading(true);
try {
walletStrategy.setWallet(wallet);
const addresses = await walletStrategy.getAddresses();
if (addresses.length > 0) {
setAddress(addresses[0]);
}
} catch (error) {
console.error('Failed to connect Injective wallet:', error);
} finally {
setIsLoading(false);
}
};

const disconnect = () => {
setAddress('');
// WalletStrategy doesn't strictly "disconnect" in the same way,
// but we clear local state.
};

return (
<InjectiveWalletContext.Provider value={{
walletStrategy,
address,
chainId,
connect,
disconnect,
isLoading
}}>
{children}
</InjectiveWalletContext.Provider>
);
};
6 changes: 6 additions & 0 deletions packages/data/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export const LENS_PERIPHERY = getEnvConfig().lensPeripheryAddress;
export const DEFAULT_COLLECT_TOKEN = getEnvConfig().defaultCollectToken;
export const LIT_PROTOCOL_ENVIRONMENT = getEnvConfig().litProtocolEnvironment;

export const INJECTIVE_ENDPOINTS = {
indexerApi: 'https://sentry.exchange.grpc-web.injective.network',
grpc: 'https://sentry.chain.grpc-web.injective.network',
rest: 'https://sentry.lcd.injective.network'
};

export const IS_MAINNET = API_URL === LensEndpoint.Mainnet;

// XMTP
Expand Down