Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { shortenAddress } from "thirdweb/utils";
import { ChainCombobox } from "@/components/ChainCombobox";
import { getChains } from "@/lib/chains";
import { client } from "@/lib/client";
import { getEcosystemChainIds } from "@/lib/ecosystemConfig";
import { getEcosystemInfo } from "@/lib/ecosystems";
import { SIMPLEHASH_NFT_SUPPORTED_CHAIN_IDS } from "@/util/simplehash";

Expand Down Expand Up @@ -45,8 +46,11 @@ export default async function Layout(props: {
thirdwebChainsPromise,
]);

const specialChainIds = getEcosystemChainIds(params.ecosystem);
const allowedChainIds = specialChainIds ?? SIMPLEHASH_NFT_SUPPORTED_CHAIN_IDS;

const simpleHashChains = thirdwebChains.filter((chain) =>
SIMPLEHASH_NFT_SUPPORTED_CHAIN_IDS.includes(chain.chainId),
allowedChainIds.includes(chain.chainId),
);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { getAddress } from "thirdweb";
import { AutoConnectWalletConnect } from "@/components/AutoConnectWalletConnect";
import NftGallery from "@/components/NftGallery";
import { getEcosystemChainIds } from "@/lib/ecosystemConfig";

export default async function Page(props: {
params: Promise<{ address: string }>;
params: Promise<{ ecosystem: string; address: string }>;
searchParams: Promise<{ chainId?: string; uri?: string }>;
}) {
const [searchParams, params] = await Promise.all([
Expand All @@ -12,12 +13,18 @@ export default async function Page(props: {
]);

const { chainId, uri } = searchParams;
const { address } = params;
const { address, ecosystem } = params;
const allowedChainIds = getEcosystemChainIds(ecosystem);
const parsedChainId = chainId ? Number(chainId) : undefined;

Comment on lines +18 to 19
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: NaN chainId can leak into NftGallery and break token queries.

Number(chainId) yields NaN for invalid inputs; current logic passes it through.

Use a guarded parse (and optionally gate by allowedChainIds):

-  const parsedChainId = chainId ? Number(chainId) : undefined;
+  const parsedChainId = (() => {
+    if (!chainId) return undefined;
+    const n = Number(chainId);
+    if (!Number.isInteger(n) || n <= 0) return undefined;
+    // Optional: also ensure it's allowed by the current ecosystem
+    if (allowedChainIds && !allowedChainIds.includes(n)) return undefined;
+    return n;
+  })();

Also consider annotating the component return type:
export default async function Page(...): Promise<JSX.Element> (outside hunk). As per coding guidelines.

🤖 Prompt for AI Agents
In apps/wallet-ui/src/app/[ecosystem]/(authed)/wallet/[address]/page.tsx around
lines 18-19, Number(chainId) can produce NaN and that NaN is passed into
NftGallery breaking token queries; replace the direct Number conversion with a
guarded parse that returns undefined for invalid/non-numeric inputs (e.g.,
parseInt/Number then isFinite check) and optionally validate against an
allowedChainIds whitelist before passing to NftGallery; additionally add an
explicit return type annotation to the component signature (export default async
function Page(...): Promise<JSX.Element>) as per coding guidelines.

return (
<>
<AutoConnectWalletConnect uri={uri} />
<NftGallery chainId={Number(chainId)} owner={getAddress(address)} />
<NftGallery
allowedChainIds={allowedChainIds}
chainId={parsedChainId}
owner={getAddress(address)}
/>
</>
);
}
5 changes: 5 additions & 0 deletions apps/wallet-ui/src/components/ConnectButton.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
"use client";
import { useTheme } from "next-themes";
import { useMemo } from "react";
import { ConnectButton as ThirdwebConnectButton } from "thirdweb/react";
import { ecosystemWallet } from "thirdweb/wallets";
import { client } from "@/lib/client";
import { getEcosystemChains } from "@/lib/ecosystemConfig";

export default function ConnectButton({
ecosystem,
}: {
ecosystem: `ecosystem.${string}`;
}) {
const { theme } = useTheme();
const chains = useMemo(() => getEcosystemChains(ecosystem), [ecosystem]);

return (
<ThirdwebConnectButton
chain={chains?.[0]}
chains={chains}
client={client}
theme={theme === "light" ? "light" : "dark"}
wallets={[ecosystemWallet(ecosystem)]}
Expand Down
19 changes: 18 additions & 1 deletion apps/wallet-ui/src/components/ConnectEmbed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,33 @@
import { useQuery } from "@tanstack/react-query";
import { useParams, useSearchParams } from "next/navigation";
import { useTheme } from "next-themes";
import { useMemo } from "react";
import type { VerifyLoginPayloadParams } from "thirdweb/auth";
import { ConnectEmbed as ThirdwebConnectEmbed } from "thirdweb/react";
import { ecosystemWallet } from "thirdweb/wallets";
import { useRouter } from "@/hooks/useRouter";
import { generatePayload, getCurrentUser, login, logout } from "@/lib/auth";
import { client } from "@/lib/client";
import { getEcosystemChains } from "@/lib/ecosystemConfig";

export function ConnectEmbed() {
const { theme } = useTheme();
const router = useRouter();
const params = useParams();
const searchParams = useSearchParams();
const ecosystemParam = params.ecosystem;
const ecosystemSlug = Array.isArray(ecosystemParam)
? ecosystemParam[0]
: ecosystemParam;

const chains = useMemo(
() => getEcosystemChains(ecosystemSlug),
[ecosystemSlug],
);

const ecosystemId = ecosystemSlug
? (`ecosystem.${ecosystemSlug}` as `ecosystem.${string}`)
: undefined;

const { data: userAddress } = useQuery({
queryFn: getCurrentUser,
Expand All @@ -29,6 +44,8 @@ export function ConnectEmbed() {

return (
<ThirdwebConnectEmbed
chain={chains?.[0]}
chains={chains}
auth={{
doLogin: async (loginParams: VerifyLoginPayloadParams) => {
const success = await login(loginParams);
Expand All @@ -46,7 +63,7 @@ export function ConnectEmbed() {
autoConnect={true}
client={client}
theme={theme === "light" ? "light" : "dark"}
wallets={[ecosystemWallet(`ecosystem.${params.ecosystem}`)]}
wallets={ecosystemId ? [ecosystemWallet(ecosystemId)] : undefined}
/>
);
}
16 changes: 14 additions & 2 deletions apps/wallet-ui/src/components/NftGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,31 @@ export function NftGalleryLoading() {
export default async function NftGallery({
owner,
chainId,
allowedChainIds,
}: {
owner: Address;
chainId?: number;
page?: number;
allowedChainIds?: number[];
}) {
const resolvedChainId =
chainId && allowedChainIds && !allowedChainIds.includes(chainId)
? undefined
: chainId;

const chainIdsToQuery =
resolvedChainId !== undefined
? [Number(resolvedChainId)]
: (allowedChainIds ?? SIMPLEHASH_NFT_SUPPORTED_CHAIN_IDS);

const erc721TokensResult = await getErc721Tokens({
chainIds: chainId ? [Number(chainId)] : SIMPLEHASH_NFT_SUPPORTED_CHAIN_IDS,
chainIds: chainIdsToQuery,
limit: 36,
owner,
});

if (erc721TokensResult.tokens.length === 0) {
return <NftGalleryEmpty chainId={chainId} />;
return <NftGalleryEmpty chainId={resolvedChainId} />;
}

return (
Expand Down
34 changes: 34 additions & 0 deletions apps/wallet-ui/src/lib/ecosystemConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Chain } from "thirdweb";
import { defineChain } from "thirdweb";

type EcosystemIdentifier = `ecosystem.${string}` | string | undefined;

const SPECIAL_ECOSYSTEM_CHAIN_IDS: Record<string, readonly number[]> = {
"mon-id": [1, 43114] as const,
Copy link
Member

Choose a reason for hiding this comment

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

fine for now, but we could totally put this in the ecosystem config. right now we have only 'default chain', but we could let ppl add all the chains they care about

};

function normalizeEcosystemSlug(ecosystem?: string) {
if (!ecosystem) {
return undefined;
}
if (ecosystem.startsWith("ecosystem.")) {
const [, slug] = ecosystem.split(".");
return slug;
}
return ecosystem;
}

export function getEcosystemChainIds(
ecosystem?: EcosystemIdentifier,
): number[] | undefined {
const slug = normalizeEcosystemSlug(ecosystem);
const chainIds = slug ? SPECIAL_ECOSYSTEM_CHAIN_IDS[slug] : undefined;
return chainIds ? [...chainIds] : undefined;
}

export function getEcosystemChains(
ecosystem?: EcosystemIdentifier,
): Chain[] | undefined {
const chainIds = getEcosystemChainIds(ecosystem);
return chainIds?.map((chainId) => defineChain(chainId));
}
Loading