Repository: BibliothecaDAO/eternum
Pull request #3181: Cancel listings not from owner, tidy token detail dialog
Author: login: RedBeardEth
Created: 2025-05-13T04:01:30Z | Merged: 2025-05-13T05:03:47Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added real-time display of active market orders for season passes, including expiration countdowns and improved price visibility.
  - Integrated resource and wonder attributes directly into the main info section of realm and pass detail modals.

- **Improvements**
  - Enhanced metadata handling for season passes and realms, resulting in more consistent and reliable display of pass and realm attributes.
  - Updated UI layouts for detail modals to present metadata, ownership, and expiration timing more clearly.

- **Bug Fixes**
  - Improved robustness when displaying pass and realm details by handling missing or undefined metadata gracefully.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/client/apps/landing/src/components/modules/marketplace-sweep-dialog.tsx b/client/apps/landing/src/components/modules/marketplace-sweep-dialog.tsx
index aef30c3511a..b0500bcdd34 100644
--- a/client/apps/landing/src/components/modules/marketplace-sweep-dialog.tsx
+++ b/client/apps/landing/src/components/modules/marketplace-sweep-dialog.tsx
@@ -44,7 +44,7 @@ export const PurchaseDialog = ({ isOpen, onOpenChange }: PurchaseDialogProps) =>
           {/* Selected Passes List */}
           <div className="max-h-[400px] overflow-y-auto space-y-3">
             {selectedPasses.map((pass) => {
-              const metadata = pass.metadata ? JSON.parse(pass.metadata) : null;
+              const metadata = pass.metadata;
               const price = pass.best_price_hex ? Number(formatUnits(BigInt(pass.best_price_hex), 18)) : 0;
 
               return (
@@ -58,7 +58,7 @@ export const PurchaseDialog = ({ isOpen, onOpenChange }: PurchaseDialogProps) =>
                     <h4 className="font-medium text-gold">{metadata?.name || `Pass #${pass.token_id}`}</h4>
                     <div className="flex items-center justify-between gap-2 text-sm text-muted-foreground">
                       <div className="flex items-center gap-2">
-                        {metadata.attributes
+                        {metadata?.attributes
                           ?.filter((attribute: { trait_type: string }) => attribute.trait_type === "Resource")
                           .sort(
                             (a: { value: { toString: () => string } }, b: { value: { toString: () => string } }) => {
@@ -71,7 +71,7 @@ export const PurchaseDialog = ({ isOpen, onOpenChange }: PurchaseDialogProps) =>
                               return rarityA - rarityB;
                             },
                           )
-                          .map((attribute: { value: string; trait_type: any }, index: any) => (
+                          .map((attribute, index: any) => (
                             <ResourceIcon
                               resource={attribute.value as string}
                               size="sm"
diff --git a/client/apps/landing/src/components/modules/realm-detail-modal.tsx b/client/apps/landing/src/components/modules/realm-detail-modal.tsx
index 19d0cdc1762..eef7b58d2f8 100644
--- a/client/apps/landing/src/components/modules/realm-detail-modal.tsx
+++ b/client/apps/landing/src/components/modules/realm-detail-modal.tsx
@@ -3,10 +3,14 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "
 import { Input } from "@/components/ui/input";
 import { Label } from "@/components/ui/label";
 import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
+import { seasonPassAddress } from "@/config";
+import { fetchActiveMarketOrders } from "@/hooks/services";
 import { useLords } from "@/hooks/use-lords";
 import { useMarketplace } from "@/hooks/use-marketplace";
-import { RealmMetadata } from "@/types";
+import { MergedNftData } from "@/types";
+import { shortenHex } from "@dojoengine/utils";
 import { useAccount, useConnect } from "@starknet-react/core";
+import { useQuery } from "@tanstack/react-query";
 import { AlertTriangle, Loader2 } from "lucide-react";
 import { useEffect, useState } from "react";
 import { toast } from "sonner";
@@ -16,21 +20,11 @@ import { ResourceIcon } from "../ui/elements/resource-icon";
 // Marketplace fee percentage
 const MARKETPLACE_FEE_PERCENT = 5;
 
-// Define the structure for realm data passed to the modal
-interface RealmModalData {
-  tokenId: string; // Pass as string
-  contractAddress?: string;
-  name?: string | null;
-  imageSrc: string;
-  attributes?: RealmMetadata["attributes"];
-  // Add any other details needed in the modal
-}
-
 // Define the props for the modal component
 interface RealmDetailModalProps {
   isOpen: boolean;
   onOpenChange: (isOpen: boolean) => void;
-  realmData: RealmModalData;
+  realmData: MergedNftData;
   isOwner: boolean;
   hasSeasonPassMinted: boolean;
   marketplaceActions: ReturnType<typeof useMarketplace>;
@@ -55,7 +49,7 @@ export const RealmDetailModal = ({
   collection_id,
   expiration, // Added
 }: RealmDetailModalProps) => {
-  const { name, tokenId, contractAddress, imageSrc, attributes } = realmData;
+  const { name, image, attributes } = realmData.metadata ?? {};
   const {
     listItem,
     cancelOrder,
@@ -67,6 +61,12 @@ export const RealmDetailModal = ({
     isApprovingMarketplace,
   } = marketplaceActions;
 
+  const { data: activeMarketOrder } = useQuery({
+    queryKey: ["activeMarketOrdersTotal", seasonPassAddress, realmData.token_id.toString()],
+    queryFn: () => fetchActiveMarketOrders(seasonPassAddress, realmData.token_id.toString()),
+    refetchInterval: 30_000,
+  });
+
   // Get wallet state
   const { address } = useAccount();
   const { connectors, connect } = useConnect();
@@ -98,6 +98,36 @@ export const RealmDetailModal = ({
   const [showEditInputs, setShowEditInputs] = useState(false);
   const [editPrice, setEditPrice] = useState(price ? formatUnits(price, 18) : ""); // Assuming price is u128, format from wei
   const [isSyncing, setIsSyncing] = useState(false);
+  const [timeRemaining, setTimeRemaining] = useState<string | null>(null);
+
+  // Calculate time remaining for auctions about to expire
+  useEffect(() => {
+    if (!expiration) return;
+
+    const expirationTime = Number(expiration) * 1000;
+    const updateCountdown = () => {
+      const now = Date.now();
+      const diff = expirationTime - now;
+
+      if (diff <= 0) {
+        setTimeRemaining("Expired");
+        return;
+      }
+
+      // Only show countdown if less than an hour remains
+      if (diff < 60 * 60 * 1000) {
+        const minutes = Math.floor((diff / (1000 * 60)) % 60);
+        const seconds = Math.floor((diff / 1000) % 60);
+        setTimeRemaining(`${minutes}m ${seconds}s remaining`);
+      } else {
+        setTimeRemaining(null);
+      }
+    };
+
+    updateCountdown();
+    const interval = setInterval(updateCountdown, 1000);
+    return () => clearInterval(interval);
+  }, [expiration]);
 
   // Reset inputs AND syncing state when modal closes or relevant props change
   useEffect(() => {
@@ -128,12 +158,17 @@ export const RealmDetailModal = ({
 
   // --- Action Handlers ---
   const handleListItem = async () => {
-    if (!contractAddress || !listPrice) return; // Basic validation
+    if (/*!contractAddress || */ !listPrice) return; // Basic validation
     const priceInWei = BigInt(parseFloat(listPrice) * 1e18); // Convert price to wei (assuming 18 decimals for LORDS)
 
+    if (activeMarketOrder?.[0]?.owner != address) {
+      await handleCancelOrder();
+      toast.success("Inactive order cancelled, now listing...");
+    }
+
     try {
       await listItem({
-        token_id: parseInt(tokenId), // Ensure tokenId is number if required by hook
+        token_id: parseInt(realmData.token_id.toString()), // Convert to string to match expected type
         collection_id, // Placeholder: Use actual collection ID if available/needed
         price: priceInWei,
         expiration: listExpiration,
@@ -166,12 +201,11 @@ export const RealmDetailModal = ({
       setIsSyncing(false);
     }
   };
-
   const handleCancelOrder = async () => {
-    if (!orderId) return; // Basic validation
+    if (!orderId && !activeMarketOrder?.[0]?.order_id) return; // Basic validation
     try {
       await cancelOrder({
-        order_id: BigInt(orderId),
+        order_id: BigInt(orderId ?? activeMarketOrder?.[0]?.order_id ?? 0),
       });
       toast.success("Transaction confirmed! Syncing cancellation...");
       setIsSyncing(true);
@@ -202,32 +236,71 @@ export const RealmDetailModal = ({
     <Dialog open={isOpen} onOpenChange={onOpenChange}>
       <DialogContent className="sm:max-w-2xl bg-card border-border text-foreground">
         <DialogHeader>
-          <DialogTitle> {name || "N/A"}</DialogTitle>
-          <div className="flex  justify-between gap-2 text-muted-foreground">
-            <p>{parseInt(tokenId?.toString())}</p>
-            <p>
-              {contractAddress?.slice(0, 4)}...{contractAddress?.slice(-4)}
-            </p>
-          </div>
+          <DialogTitle>Season 1 Pass</DialogTitle>
         </DialogHeader>
         <div className="grid gap-4 py-4">
-          <img src={imageSrc} alt={name ?? "Realm"} className="rounded-md w-72 mb-4 object-contain mx-auto" />
-
+          <div className="grid grid-cols-2 gap-4 pb-2">
+            <img src={image} alt={name ?? "Realm"} className="rounded-md w-72 object-contain mx-auto" />
+            <div className="flex flex-col gap-5">
+              <div className=" leading-none tracking-tight items-center gap-2">
+                <h3 className="text-gold font-semibold text-2xl">{name || "N/A"}</h3>
+                <p className="text-muted-foreground">#{parseInt(realmData.token_id?.toString())}</p>
+              </div>
+              {/* Display Resources */}
+              <div>
+                <Label className="uppercase tracking-wider mb-1 flex justify-between items-center text-muted-foreground text-xs">
+                  Resources
+                </Label>
+                <div className="flex flex-wrap gap-2">
+                  {attributes
+                    ?.filter((attribute) => attribute.trait_type === "Resource")
+                    .map((attribute, index) => (
+                      <ResourceIcon
+                        resource={attribute.value as string}
+                        size="lg"
+                        key={`${attribute.trait_type}-${index}`}
+                      />
+                    ))}
+                </div>
+              </div>
+              {/* Display Wonder */}
+              {attributes?.find((attribute) => attribute.trait_type === "Wonder")?.value && (
+                <div className="border-t items-center flex uppercase flex-wrap w-full py-2 justify-center text-center text-sm bg-crimson/50 rounded-lg">
+                  Wonder:{" "}
+                  <span className="text-gold font-bold tracking-wide">
+                    {attributes.find((attribute) => attribute.trait_type === "Wonder")?.value}
+                  </span>
+                </div>
+              )}
+              <div>
+                <Label className="uppercase tracking-wider mb-1 flex justify-between items-center text-muted-foreground text-xs">
+                  Owned By
+                </Label>
+                <span>{shortenHex(realmData.token_owner ?? "", 10)}</span>
+              </div>
+            </div>
+          </div>
           {/* Display Price if Listed */}
           {isListed && price !== undefined ? (
-            <div className="text-center border-t border-b py-3 my-3">
+            <div className="border-t pt-6 px-4">
               <p className="text-sm text-muted-foreground uppercase tracking-wider">Price</p>
-              <div className="text-4xl font-bold text-gold gap-2 mx-auto text-center flex items-center justify-center">
-                <div>
+              <div className="flex items-center gap-2">
+                <div className="text-3xl font-bold text-gold -mt-2">
                   {isSyncing
                     ? "Syncing..."
                     : parseFloat(formatUnits(price, 18)).toLocaleString("en-US", {
                         minimumFractionDigits: 2,
                         maximumFractionDigits: 2,
-                      })}{" "}
+                      })}
                 </div>
-
                 <ResourceIcon resource="Lords" size="sm" />
+                <div className="text-sm text-muted-foreground">
+                  {timeRemaining ? (
+                    <span className="text-red-500 font-medium">{timeRemaining}</span>
+                  ) : (
+                    `Expires ${new Date(Number(expiration) * 1000).toLocaleString()}`
+                  )}
+                </div>
               </div>
               <p className="text-xs text-muted-foreground mt-1">Includes {MARKETPLACE_FEE_PERCENT}% marketplace fee</p>
             </div>
@@ -237,23 +310,6 @@ export const RealmDetailModal = ({
               <p className="text-2xl font-bold text-gold">Not for sale</p>
             </div>
           )}
-          {/* Display Resources */}
-          <div className="flex flex-wrap gap-2 mb-2 justify-center">
-            {attributes
-              ?.filter((attribute) => attribute.trait_type === "Resource")
-              .map((attribute, index) => (
-                <ResourceIcon resource={attribute.value as string} size="lg" key={`${attribute.trait_type}-${index}`} />
-              ))}
-          </div>
-          {/* Display Wonder */}
-          {attributes?.find((attribute) => attribute.trait_type === "Wonder")?.value && (
-            <div className="border-t pt-2 mt-2 text-center text-sm uppercase">
-              Wonder:{" "}
-              <span className="text-gold font-bold tracking-wide">
-                {attributes.find((attribute) => attribute.trait_type === "Wonder")?.value}
-              </span>
-            </div>
-          )}
         </div>
         <DialogFooter className="flex-col sm:flex-row sm:justify-between items-stretch pt-4 border-t">
           <div className="flex-grow flex flex-col gap-2 order-2 sm:order-1 mb-4 sm:mb-0">
diff --git a/client/apps/landing/src/components/modules/season-pass-card.tsx b/client/apps/landing/src/components/modules/season-pass-card.tsx
index 38be55e8b21..2c7e774f844 100644
--- a/client/apps/landing/src/components/modules/season-pass-card.tsx
+++ b/client/apps/landing/src/components/modules/season-pass-card.tsx
@@ -2,7 +2,7 @@ import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/componen
 import { seasonPassAddress } from "@/config";
 import { useMarketplace } from "@/hooks/use-marketplace";
 import { trimAddress } from "@/lib/utils";
-import { MergedNftData, RealmMetadata } from "@/types";
+import { MergedNftData } from "@/types";
 import { RESOURCE_RARITY, ResourcesIds } from "@bibliothecadao/types"; // Import enums
 import { useAccount } from "@starknet-react/core";
 import { ArrowRightLeft, Check, Plus } from "lucide-react"; // Import the icon
@@ -84,8 +84,7 @@ export const SeasonPassCard = ({
     }
   };
 
-  const parsedMetadata: RealmMetadata | null = metadata ? JSON.parse(metadata) : null;
-  const { attributes, name, image } = parsedMetadata ?? {};
+  const { attributes, name, image } = metadata ?? {};
 
   // Prepare data for the modal (useMemo)
   const modalData = useMemo(
@@ -100,11 +99,11 @@ export const SeasonPassCard = ({
   );
 
   const listingActive = useMemo(() => {
-    if (pass.expiration !== null && timeRemaining !== "Expired" && pass.best_price_hex !== null) {
+    if (pass.expiration !== null && pass.best_price_hex !== null) {
       return true;
     }
     return false;
-  }, [pass.expiration, timeRemaining, pass.best_price_hex]);
+  }, [pass.expiration, pass.best_price_hex]);
 
   return (
     <>
@@ -232,7 +231,7 @@ export const SeasonPassCard = ({
       <RealmDetailModal
         isOpen={isModalOpen}
         onOpenChange={setIsModalOpen}
-        realmData={modalData}
+        realmData={pass}
         isOwner={isOwner}
         hasSeasonPassMinted={false}
         marketplaceActions={marketplaceActions}
diff --git a/client/apps/landing/src/components/modules/transfer-season-pass-dialog.tsx b/client/apps/landing/src/components/modules/transfer-season-pass-dialog.tsx
index 1ad4f4711fd..6581962d567 100644
--- a/client/apps/landing/src/components/modules/transfer-season-pass-dialog.tsx
+++ b/client/apps/landing/src/components/modules/transfer-season-pass-dialog.tsx
@@ -144,9 +144,7 @@ export default function TransferSeasonPassDialog({
             </TableHeader>
             <TableBody>
               {seasonPassMints?.map((seasonPassMint) => {
-                const parsedMetadata: RealmMetadata | null = seasonPassMint?.metadata
-                  ? JSON.parse(seasonPassMint?.metadata)
-                  : null;
+                const parsedMetadata: RealmMetadata | null = seasonPassMint?.metadata;
                 const { attributes, name } = parsedMetadata ?? {};
                 const tokenId = seasonPassMint?.token_id.toString();
 
diff --git a/client/apps/landing/src/hooks/services/index.ts b/client/apps/landing/src/hooks/services/index.ts
index e7ccde96e06..8c2cce5bba3 100644
--- a/client/apps/landing/src/hooks/services/index.ts
+++ b/client/apps/landing/src/hooks/services/index.ts
@@ -1,4 +1,5 @@
 import { trimAddress } from "@/lib/utils";
+import { RealmMetadata } from "@/types";
 import { ContractAddress, HexPosition, ID } from "@bibliothecadao/types";
 import { env } from "../../../env";
 
@@ -9,6 +10,20 @@ const QUERIES = {
   REALM_SETTLEMENTS: "SELECT `base.coord_x`, `base.coord_y`, owner FROM [s1_eternum-Structure] WHERE category == 1;",
   REALM_VILLAGE_SLOTS:
     "SELECT `connected_realm_coord.x`, `connected_realm_coord.y`, connected_realm_entity_id, connected_realm_id, directions_left FROM `s1_eternum-StructureVillageSlots`",
+  ACTIVE_MARKET_ORDERS: `
+    SELECT 
+      mo.order_id AS order_id,
+      mo."order.token_id" AS token_id, 
+      mo."order.price" AS price,
+      mo."order.owner" AS owner,
+      mo."order.expiration" AS expiration,
+      mo."order.collection_id" AS collection_id
+    FROM "marketplace-MarketOrderModel" AS mo
+    WHERE mo."active" = 1
+      AND mo."collection_id" = 1  
+      AND mo."token_id" = '{tokenId}'
+    ORDER BY mo."price" ASC
+  `,
   TOKEN_TRANSFERS: `
     WITH token_meta AS ( 
         SELECT contract_address,
@@ -253,7 +268,7 @@ export interface OpenOrderByPrice {
   order_id: number;
   name: string | null;
   symbol: string | null;
-  metadata: string | null;
+  metadata: RealmMetadata | null;
   best_price_hex: bigint | null;
   expiration: number | null;
   token_owner: string | null;
@@ -401,6 +416,7 @@ export async function fetchOpenOrdersByPrice(
     best_price_hex: item.price_hex ? BigInt(item.price_hex) : null,
     token_owner: item.token_owner?.toString() ?? null,
     order_owner: item.order_owner?.toString() ?? null,
+    metadata: item.metadata ? JSON.parse(item.metadata) : null,
   }));
 }
 
@@ -435,7 +451,32 @@ export interface TokenBalanceWithToken {
   symbol: string | null;
   expiration: number | null;
   best_price_hex: bigint | null;
-  metadata: string | null;
+  metadata: RealmMetadata | null;
+}
+
+export interface ActiveMarketOrder {
+  order_id: string;
+  token_id: string;
+  price: string;
+  owner: string;
+  expiration: number;
+  collection_id: number;
+}
+
+export async function fetchActiveMarketOrders(contractAddress: string, tokenId: string): Promise<ActiveMarketOrder[]> {
+  const query = QUERIES.ACTIVE_MARKET_ORDERS.replace("{contractAddress}", contractAddress).replace(
+    "{tokenId}",
+    tokenId,
+  );
+
+  const url = `${API_BASE_URL}?query=${encodeURIComponent(query)}`;
+  const response = await fetch(url);
+
+  if (!response.ok) {
+    throw new Error(`Failed to fetch active market orders: ${response.statusText}`);
+  }
+
+  return await response.json();
 }
 
 export async function fetchTokenBalancesWithMetadata(
@@ -445,16 +486,19 @@ export async function fetchTokenBalancesWithMetadata(
   const query = QUERIES.TOKEN_BALANCES_WITH_METADATA.replaceAll("{contractAddress}", contractAddress)
     .replace("{accountAddress}", accountAddress)
     .replace("{trimmedAccountAddress}", trimAddress(accountAddress));
-  console.log(query);
+
   const url = `${API_BASE_URL}?query=${encodeURIComponent(query)}`;
   const response = await fetch(url);
   if (!response.ok) {
     throw new Error(`Failed to fetch token balances with tokens: ${response.statusText}`);
   }
   const rawData = await response.json();
-  return rawData.map((item: TokenBalanceWithToken) => ({
-    ...item,
-    token_id: parseInt(item.token_id?.split(":")[1] ?? "0", 16),
-    best_price_hex: item.best_price_hex ? BigInt(item.best_price_hex) : null,
-  }));
+  return rawData.map(
+    (item: { token_id: string; best_price_hex: string | number | bigint | boolean; metadata: string }) => ({
+      ...item,
+      token_id: parseInt(item.token_id?.split(":")[1] ?? "0", 16),
+      best_price_hex: item.best_price_hex ? BigInt(item.best_price_hex) : null,
+      metadata: item.metadata ? JSON.parse(item.metadata) : null,
+    }),
+  );
 }
diff --git a/client/apps/landing/src/hooks/useTraitFiltering.ts b/client/apps/landing/src/hooks/useTraitFiltering.ts
index 2529c4ec777..774a998ece4 100644
--- a/client/apps/landing/src/hooks/useTraitFiltering.ts
+++ b/client/apps/landing/src/hooks/useTraitFiltering.ts
@@ -1,3 +1,4 @@
+import { RealmMetadata } from "@/types";
 import { useCallback, useMemo, useState } from "react";
 
 interface Trait {
@@ -28,7 +29,7 @@ interface UseTraitFilteringReturn<T> {
  */
 export function useTraitFiltering<T>(
   data: T[] | undefined | null,
-  getMetadataString: (item: T) => string | undefined | null,
+  getMetadataString: (item: T) => RealmMetadata | null,
 ): UseTraitFilteringReturn<T> {
   const [selectedFilters, setSelectedFilters] = useState<Record<string, string[]>>({});
 
@@ -39,7 +40,7 @@ export function useTraitFiltering<T>(
       const metadataString = getMetadataString(item);
       if (metadataString) {
         try {
-          const metadata: Metadata = JSON.parse(metadataString);
+          const metadata: Metadata = metadataString;
           if (metadata.attributes && Array.isArray(metadata.attributes)) {
             metadata.attributes.forEach((attr) => {
               if (attr.trait_type && attr.value !== undefined && attr.value !== null) {
@@ -89,7 +90,7 @@ export function useTraitFiltering<T>(
       let attributes: Trait[] = [];
       if (metadataString) {
         try {
-          const metadata: Metadata = JSON.parse(metadataString);
+          const metadata = metadataString;
           if (metadata.attributes && Array.isArray(metadata.attributes)) {
             attributes = metadata.attributes;
           }
diff --git a/client/apps/landing/src/routes/mint.lazy.tsx b/client/apps/landing/src/routes/mint.lazy.tsx
index a6318013172..6370f94a774 100644
--- a/client/apps/landing/src/routes/mint.lazy.tsx
+++ b/client/apps/landing/src/routes/mint.lazy.tsx
@@ -77,8 +77,9 @@ function Mint() {
   const realmsErcBalance = seasonPassMints.data;
 
   // --- Filtering Hook ---
-  const getRealmMetadataString = useCallback((realm: SeasonPassRealm): string | null => {
-    return realm.metadata;
+  const getRealmMetadataString = useCallback((realm: SeasonPassRealm) => {
+    if (!realm.metadata) return null;
+    return JSON.parse(realm.metadata);
   }, []);
 
   const {
diff --git a/client/apps/landing/src/routes/season-passes.lazy.tsx b/client/apps/landing/src/routes/season-passes.lazy.tsx
index 8a97bdd3fd9..2113f8095a0 100644
--- a/client/apps/landing/src/routes/season-passes.lazy.tsx
+++ b/client/apps/landing/src/routes/season-passes.lazy.tsx
@@ -60,7 +60,7 @@ function SeasonPasses() {
     ],
   });
 
-  const getSeasonPassMetadataString = useCallback((pass: MergedNftData): string | null => {
+  const getSeasonPassMetadataString = useCallback((pass: MergedNftData) => {
     if (pass?.metadata) {
       return pass.metadata;
     }
diff --git a/client/apps/landing/src/routes/trade.lazy.tsx b/client/apps/landing/src/routes/trade.lazy.tsx
index b7303d629f8..1ba356222a3 100644
--- a/client/apps/landing/src/routes/trade.lazy.tsx
+++ b/client/apps/landing/src/routes/trade.lazy.tsx
@@ -63,7 +63,7 @@ function SeasonPasses() {
 
   //const mySeasonPassNfts: TokenBalanceEdge[] = useMemo(() => getSeasonPassNfts(myNftsQuery.data), [myNftsQuery.data]);
 
-  const getSeasonPassMetadataString = useCallback((pass: OpenOrderByPrice): string | null => {
+  const getSeasonPassMetadataString = useCallback((pass: OpenOrderByPrice) => {
     if (pass?.metadata) {
       return pass.metadata;
     }
@@ -352,7 +352,7 @@ function SeasonPasses() {
                       {/* Selected Pass Images */}
                       <div className="flex -space-x-2">
                         {selectedPasses.slice(0, 3).map((pass) => {
-                          const metadata = pass.metadata ? JSON.parse(pass.metadata) : null;
+                          const metadata = pass.metadata;
                           const image = metadata?.image;
                           return (
                             <div
diff --git a/client/apps/landing/src/types/index.ts b/client/apps/landing/src/types/index.ts
index 90f7bf85ab3..46a18b04603 100644
--- a/client/apps/landing/src/types/index.ts
+++ b/client/apps/landing/src/types/index.ts
@@ -32,7 +32,7 @@ export interface MarketOrder {
 }
 
 export interface MergedNftData {
-  metadata: string | null;
+  metadata: RealmMetadata | null;
   minPrice?: bigint | null;
   marketplaceOwner?: string | null;
   order_id?: number | null;


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🔭 Outside diff range comments (3)</summary><blockquote>

<details>
<summary>client/apps/landing/src/routes/mint.lazy.tsx (1)</summary><blockquote>

`113-116`: _⚠️ Potential issue_

**Double JSON parsing will cause runtime error.**

The function `getRealmMetadataString` already returns a parsed object (not a string), but here you're trying to parse it again with `JSON.parse(metaString)`. This will cause a runtime error as you can't parse an already parsed JavaScript object.

Fix by directly using the returned metadata object:

```diff
-const metaString = getRealmMetadataString(realm);
-if (metaString) {
-  parsedMetadata = JSON.parse(metaString);
+const metadata = getRealmMetadataString(realm);
+if (metadata) {
+  parsedMetadata = metadata;
 }
```

</blockquote></details>
<details>
<summary>client/apps/landing/src/hooks/services/index.ts (2)</summary><blockquote>

`411-420`: _⚠️ Potential issue_

**`order_id` is parsed as hexadecimal but is stored/returned as decimal**

`parseInt(item.order_id, 16)` will mangle IDs (e.g. `"15"` → `21`). Unless the DB stores the field as a hex string (it appears numeric), use base-10 or keep it as a `bigint`.

```diff
-    order_id: parseInt(item.order_id, 16),
+    order_id: Number(item.order_id),           // or BigInt(item.order_id)
```

Additionally, converting potentially large `token_id_hex` via `parseInt(...,16)` risks losing precision (>2^53-1). Use `BigInt` instead or keep the hex string around.

---

`445-455`: _🛠️ Refactor suggestion_

**Type contract violates implementation – `token_id` declared as `string`, returned as `number`**

`TokenBalanceWithToken` marks `token_id: string`, yet the mapper below casts it to a decimal number.  
Either:

```diff
-export interface TokenBalanceWithToken {
-  token_id: string;
+export interface TokenBalanceWithToken {
+  token_id: number;          // or bigint
```

or return the original hex/string to stay type-safe.  
This mismatch will surface as a TypeScript error once strict mode / CI is enabled.

</blockquote></details>

</blockquote></details>

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>client/apps/landing/src/components/modules/realm-detail-modal.tsx (1)</summary><blockquote>

`186-196`: **Duplicate fractional-to-BigInt bug in edit flow**

`handleEditOrder` repeats the unsafe conversion. Replace with `parseUnits(editPrice,18)` as shown above for consistency.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>client/apps/landing/src/components/modules/transfer-season-pass-dialog.tsx (1)</summary><blockquote>

`147-147`: **Removed unnecessary JSON parsing**

With `metadata` now being of type `RealmMetadata`, direct access to the metadata object eliminates the need for JSON parsing. This simplifies the code and improves performance by avoiding unnecessary parsing operations.


Consider renaming the variable from `parsedMetadata` to just `metadata` since we're no longer parsing anything:

```diff
-const parsedMetadata: RealmMetadata | null = seasonPassMint?.metadata;
+const metadata: RealmMetadata | null = seasonPassMint?.metadata;
```

</blockquote></details>
<details>
<summary>client/apps/landing/src/routes/season-passes.lazy.tsx (1)</summary><blockquote>

`63-68`: **Function name doesn't match its purpose**

Now that `metadata` is an object rather than a string, the function name `getSeasonPassMetadataString` is misleading since it's not returning a string anymore.


Consider renaming to better reflect its purpose:

```diff
-const getSeasonPassMetadataString = useCallback((pass: MergedNftData) => {
+const getSeasonPassMetadata = useCallback((pass: MergedNftData) => {
  if (pass?.metadata) {
    return pass.metadata;
  }
  return null;
}, []);
```

</blockquote></details>
<details>
<summary>client/apps/landing/src/components/modules/marketplace-sweep-dialog.tsx (1)</summary><blockquote>

`74-74`: **Consider using a specific type instead of `any`.**

While the code works, using `any` for the index type bypasses TypeScript's type checking. Consider using a more specific type like `number` instead.

```diff
-  .map((attribute, index: any) => (
+  .map((attribute, index: number) => (
```

</blockquote></details>
<details>
<summary>client/apps/landing/src/hooks/useTraitFiltering.ts (4)</summary><blockquote>

`25-29`: **Update JSDoc comment to reflect new function behavior.**

The JSDoc comment still indicates that `getMetadataString` returns a JSON string, but the function now returns a parsed `RealmMetadata` object.

```diff
 /**
  * A hook to manage trait-based filtering for an array of data.
  *
  * @param data The raw array of data items to filter.
- * @param getMetadataString A function that takes a data item and returns its metadata string (expected to be JSON).
+ * @param getMetadataString A function that takes a data item and returns its metadata as a RealmMetadata object or null.
  *
  * @returns An object containing filter state, extracted traits, filtered data, and handler functions.
  */
```

---

`30-33`: **Rename parameter to match its behavior.**

The parameter name `getMetadataString` is misleading since it no longer returns a string but a `RealmMetadata` object.

```diff
 export function useTraitFiltering<T>(
   data: T[] | undefined | null,
-  getMetadataString: (item: T) => RealmMetadata | null,
+  getMetadata: (item: T) => RealmMetadata | null,
 ): UseTraitFilteringReturn<T> {
```

Make sure to update all references to this parameter throughout the function.

---

`42-62`: **Simplify error handling since JSON parsing is removed.**

Since you're no longer parsing JSON, the try/catch block is redundant. You can simplify this code by removing it and adding null/undefined checks instead.

```diff
 (data || []).forEach((item) => {
   const metadataString = getMetadataString(item);
   if (metadataString) {
-    try {
-      const metadata: Metadata = metadataString;
-      if (metadata.attributes && Array.isArray(metadata.attributes)) {
-        metadata.attributes.forEach((attr) => {
-          if (attr.trait_type && attr.value !== undefined && attr.value !== null) {
-            const traitType = String(attr.trait_type);
-            const traitValue = String(attr.value);
-            // Skip empty string values to avoid conflicts with placeholder
-            if (traitValue !== "") {
-              if (!traitsMap[traitType]) {
-                traitsMap[traitType] = new Set();
-              }
-              traitsMap[traitType].add(traitValue);
-            }
-          }
-        });
-      }
-    } catch (e) {
-      console.error("Failed to parse metadata in useTraitFiltering:", e, metadataString);
-    }
+    const metadata: Metadata = metadataString;
+    if (metadata.attributes && Array.isArray(metadata.attributes)) {
+      metadata.attributes.forEach((attr) => {
+        if (attr.trait_type && attr.value !== undefined && attr.value !== null) {
+          const traitType = String(attr.trait_type);
+          const traitValue = String(attr.value);
+          // Skip empty string values to avoid conflicts with placeholder
+          if (traitValue !== "") {
+            if (!traitsMap[traitType]) {
+              traitsMap[traitType] = new Set();
+            }
+            traitsMap[traitType].add(traitValue);
+          }
+        }
+      });
+    }
   }
 });
```

---

`92-99`: **Remove unnecessary try/catch for JSON parsing.**

Since JSON parsing is no longer performed, the try/catch block can be removed.

```diff
 if (metadataString) {
-  try {
-    const metadata = metadataString;
-    if (metadata.attributes && Array.isArray(metadata.attributes)) {
-      attributes = metadata.attributes;
-    }
-  } catch (e) {
-    // Error already logged during trait extraction
-  }
+  const metadata = metadataString;
+  if (metadata.attributes && Array.isArray(metadata.attributes)) {
+    attributes = metadata.attributes;
+  }
 }
```

</blockquote></details>
<details>
<summary>client/apps/landing/src/components/modules/realm-detail-modal.tsx (1)</summary><blockquote>

`64-68`: **Query key naming mismatch may cause stale-cache collisions**

`queryKey: ["activeMarketOrdersTotal", …]` actually fetches individual active orders, not the “total”.  
Consider renaming to avoid confusing React-Query’s cache and any devs reading the code:

```diff
-queryKey: ["activeMarketOrdersTotal", seasonPassAddress, realmData.token_id.toString()],
+queryKey: ["activeMarketOrders", seasonPassAddress, realmData.token_id.toString()],
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 7a6a40d320c514bf56223f33c09960838e1a9453 and fc54dd2061348e7f449c4f397cd4d50b4f9c4eba.

</details>

<details>
<summary>📒 Files selected for processing (10)</summary>

* `client/apps/landing/src/components/modules/marketplace-sweep-dialog.tsx` (3 hunks)
* `client/apps/landing/src/components/modules/realm-detail-modal.tsx` (8 hunks)
* `client/apps/landing/src/components/modules/season-pass-card.tsx` (4 hunks)
* `client/apps/landing/src/components/modules/transfer-season-pass-dialog.tsx` (1 hunks)
* `client/apps/landing/src/hooks/services/index.ts` (6 hunks)
* `client/apps/landing/src/hooks/useTraitFiltering.ts` (4 hunks)
* `client/apps/landing/src/routes/mint.lazy.tsx` (1 hunks)
* `client/apps/landing/src/routes/season-passes.lazy.tsx` (1 hunks)
* `client/apps/landing/src/routes/trade.lazy.tsx` (2 hunks)
* `client/apps/landing/src/types/index.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (7)</summary>

<details>
<summary>client/apps/landing/src/components/modules/transfer-season-pass-dialog.tsx (1)</summary><blockquote>

<details>
<summary>client/apps/landing/src/types/index.ts (1)</summary>

* `RealmMetadata` (1-9)

</details>

</blockquote></details>
<details>
<summary>client/apps/landing/src/routes/trade.lazy.tsx (1)</summary><blockquote>

<details>
<summary>client/apps/landing/src/hooks/services/index.ts (1)</summary>

* `OpenOrderByPrice` (266-277)

</details>

</blockquote></details>
<details>
<summary>client/apps/landing/src/routes/season-passes.lazy.tsx (1)</summary><blockquote>

<details>
<summary>client/apps/landing/src/types/index.ts (1)</summary>

* `MergedNftData` (34-44)

</details>

</blockquote></details>
<details>
<summary>client/apps/landing/src/routes/mint.lazy.tsx (1)</summary><blockquote>

<details>
<summary>client/apps/landing/src/hooks/services/index.ts (1)</summary>

* `SeasonPassRealm` (285-292)

</details>

</blockquote></details>
<details>
<summary>client/apps/landing/src/hooks/useTraitFiltering.ts (1)</summary><blockquote>

<details>
<summary>client/apps/landing/src/types/index.ts (1)</summary>

* `RealmMetadata` (1-9)

</details>

</blockquote></details>
<details>
<summary>client/apps/landing/src/components/modules/realm-detail-modal.tsx (4)</summary><blockquote>

<details>
<summary>client/apps/landing/src/types/index.ts (1)</summary>

* `MergedNftData` (34-44)

</details>
<details>
<summary>packages/react/src/hooks/helpers/use-query.ts (1)</summary>

* `useQuery` (5-39)

</details>
<details>
<summary>client/apps/landing/src/config.ts (1)</summary>

* `seasonPassAddress` (9-9)

</details>
<details>
<summary>client/apps/landing/src/hooks/services/index.ts (1)</summary>

* `fetchActiveMarketOrders` (466-480)

</details>

</blockquote></details>
<details>
<summary>client/apps/landing/src/hooks/services/index.ts (1)</summary><blockquote>

<details>
<summary>client/apps/landing/src/types/index.ts (1)</summary>

* `RealmMetadata` (1-9)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Run pr agent on every pull request, respond to user comments

</details>

<details>
<summary>🔇 Additional comments (9)</summary><blockquote>

<details>
<summary>client/apps/landing/src/types/index.ts (1)</summary>

`35-35`: **Type change improves type safety for metadata**

The `metadata` property in `MergedNftData` now uses a structured `RealmMetadata` type instead of a string. This change improves type safety and provides better IntelliSense support when accessing metadata properties.

</details>
<details>
<summary>client/apps/landing/src/routes/trade.lazy.tsx (2)</summary>

`66-71`: **Return type annotation removed**

The explicit return type was removed as it's now inferred from the return value. This simplifies the code while maintaining type safety through TypeScript's inference.

---

`355-356`: **Direct metadata object usage**

Simplified access to metadata by using the object directly instead of parsing JSON. This aligns with the type changes made to `MergedNftData`.

</details>
<details>
<summary>client/apps/landing/src/components/modules/season-pass-card.tsx (3)</summary>

`101-106`: **LGTM! Simplified listing status check.**

The logic for determining if a listing is active has been simplified to only check if both `pass.expiration` and `pass.best_price_hex` are non-null, which is cleaner than the previous implementation.

---

`87-87`: **LGTM! Direct metadata access.**

Correctly updated to access metadata as an object rather than parsing from a JSON string, aligning with the broader refactoring of metadata handling.

---

`234-234`: **LGTM! More consistent data passing to modal.**

Now passing the entire `pass` object to the `RealmDetailModal` component, which is a cleaner approach as the modal can extract whatever data it needs directly.

</details>
<details>
<summary>client/apps/landing/src/components/modules/marketplace-sweep-dialog.tsx (2)</summary>

`47-48`: **LGTM! Direct metadata access.**

Correctly updated to access metadata directly as an object rather than parsing from a JSON string.

---

`61-62`: **LGTM! Added null safety with optional chaining.**

Good addition of optional chaining for accessing attributes, which prevents potential runtime errors if metadata is null or undefined.

</details>
<details>
<summary>client/apps/landing/src/hooks/services/index.ts (1)</summary>

`466-474`: **Redundant `.replace()` call hides a potential security issue**

Because `{contractAddress}` does not exist in the template, the following line silently does nothing:

```ts
QUERIES.ACTIVE_MARKET_ORDERS.replace("{contractAddress}", contractAddress)
```

That makes it easy to overlook the bug highlighted above and also invites SQL-injection style mistakes (direct string concatenation without sanitisation).  
After fixing the placeholder mismatch, prefer `encodeURIComponent` or a server-side parameterised query if available.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T04:06:03Z

Review comments:
diff_hunk: @@ -77,8 +77,9 @@ function Mint() {
   const realmsErcBalance = seasonPassMints.data;
 
   // --- Filtering Hook ---
-  const getRealmMetadataString = useCallback((realm: SeasonPassRealm): string | null => {
-    return realm.metadata;
+  const getRealmMetadataString = useCallback((realm: SeasonPassRealm) => {
+    if (!realm.metadata) return null;
+    return JSON.parse(realm.metadata);
   }, []);
path: client/apps/landing/src/routes/mint.lazy.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix metadata parsing and update function name.**

The function `getRealmMetadataString` now returns a parsed object, not a string. However, line 115 tries to parse this result again with `JSON.parse`, which will cause a runtime error.

1. Rename the function to match its behavior (e.g., `getRealmMetadata`)
2. Fix the double parsing issue in line 115

```diff
-const getRealmMetadataString = useCallback((realm: SeasonPassRealm) => {
+const getRealmMetadata = useCallback((realm: SeasonPassRealm) => {
   if (!realm.metadata) return null;
   return JSON.parse(realm.metadata);
 }, []);

 // And then further down in line 113-115:
-const metaString = getRealmMetadataString(realm);
-if (metaString) {
-  parsedMetadata = JSON.parse(metaString);
+const metadata = getRealmMetadata(realm);
+if (metadata) {
+  parsedMetadata = metadata;
 }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
// Rename and return parsed metadata object
const getRealmMetadata = useCallback((realm: SeasonPassRealm) => {
  if (!realm.metadata) return null;
  return JSON.parse(realm.metadata);
}, []);

// …later in the augmentedAndSortedRealms useMemo hook…

// Replace double‐parsing with direct use of the parsed object
const metadata = getRealmMetadata(realm);
if (metadata) {
  parsedMetadata = metadata;
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-13T04:06:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9,6 +10,20 @@ const QUERIES = {
   REALM_SETTLEMENTS: "SELECT `base.coord_x`, `base.coord_y`, owner FROM [s1_eternum-Structure] WHERE category == 1;",
   REALM_VILLAGE_SLOTS:
     "SELECT `connected_realm_coord.x`, `connected_realm_coord.y`, connected_realm_entity_id, connected_realm_id, directions_left FROM `s1_eternum-StructureVillageSlots`",
+  ACTIVE_MARKET_ORDERS: `
+    SELECT 
+      mo.order_id AS order_id,
+      mo."order.token_id" AS token_id, 
+      mo."order.price" AS price,
+      mo."order.owner" AS owner,
+      mo."order.expiration" AS expiration,
+      mo."order.collection_id" AS collection_id
+    FROM "marketplace-MarketOrderModel" AS mo
+    WHERE mo."active" = 1
+      AND mo."collection_id" = 1  
+      AND mo."token_id" = '{tokenId}'
+    ORDER BY mo."price" ASC
+  `,
path: client/apps/landing/src/hooks/services/index.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`{contractAddress}` placeholder never substituted – query will ignore caller-supplied contract address**

`ACTIVE_MARKET_ORDERS` does not contain a `{contractAddress}` token, yet `fetchActiveMarketOrders` tries to replace it.  
As a result:

1. The replacement call is a no-op, so the SQL will always query `collection_id = 1` irrespective of the contract passed in.
2. Callers may believe the query is contract-scoped when it is not, producing incorrect UI state and possible information disclosure.

```diff
-      AND mo."collection_id" = 1  
+      AND mo."collection_id" = {collectionId}
+      AND mo."order.contract_address" = '{contractAddress}'
```
(Or drop the `.replace()` in the fetcher if the contract address truly is irrelevant.)

Please review the business logic and make the placeholder & substitution consistent.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
  ACTIVE_MARKET_ORDERS: `
    SELECT 
      mo.order_id AS order_id,
      mo."order.token_id" AS token_id, 
      mo."order.price" AS price,
      mo."order.owner" AS owner,
      mo."order.expiration" AS expiration,
      mo."order.collection_id" AS collection_id
    FROM "marketplace-MarketOrderModel" AS mo
    WHERE mo."active" = 1
      AND mo."collection_id" = {collectionId}
      AND mo."order.contract_address" = '{contractAddress}'
      AND mo."token_id" = '{tokenId}'
    ORDER BY mo."price" ASC
  `,
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-13T04:06:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -445,16 +486,19 @@ export async function fetchTokenBalancesWithMetadata(
   const query = QUERIES.TOKEN_BALANCES_WITH_METADATA.replaceAll("{contractAddress}", contractAddress)
     .replace("{accountAddress}", accountAddress)
     .replace("{trimmedAccountAddress}", trimAddress(accountAddress));
-  console.log(query);
+
   const url = `${API_BASE_URL}?query=${encodeURIComponent(query)}`;
   const response = await fetch(url);
   if (!response.ok) {
     throw new Error(`Failed to fetch token balances with tokens: ${response.statusText}`);
   }
   const rawData = await response.json();
-  return rawData.map((item: TokenBalanceWithToken) => ({
-    ...item,
-    token_id: parseInt(item.token_id?.split(":")[1] ?? "0", 16),
-    best_price_hex: item.best_price_hex ? BigInt(item.best_price_hex) : null,
-  }));
+  return rawData.map(
+    (item: { token_id: string; best_price_hex: string | number | bigint | boolean; metadata: string }) => ({
+      ...item,
+      token_id: parseInt(item.token_id?.split(":")[1] ?? "0", 16),
+      best_price_hex: item.best_price_hex ? BigInt(item.best_price_hex) : null,
+      metadata: item.metadata ? JSON.parse(item.metadata) : null,
+    }),
+  );
path: client/apps/landing/src/hooks/services/index.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Un-guarded `JSON.parse` & precision loss**

1. An invalid or empty `metadata` string will throw and break the whole fetch.  
2. `parseInt(token_id_hex, 16)` may lose precision on 64-bit token IDs.

Suggested safer mapping:

```diff
 return rawData.map((item) => {
+  let parsedMeta: RealmMetadata | null = null;
+  try {
+    parsedMeta = item.metadata ? JSON.parse(item.metadata) : null;
+  } catch (_) {
+    console.warn("Invalid metadata JSON for token", item.token_id);
+  }
   return {
     ...item,
-    token_id: parseInt(item.token_id?.split(":")[1] ?? "0", 16),
-    best_price_hex: item.best_price_hex ? BigInt(item.best_price_hex) : null,
-    metadata: item.metadata ? JSON.parse(item.metadata) : null,
+    token_id: BigInt(`0x${item.token_id.split(":")[1] ?? "0"}`),
+    best_price_hex: item.best_price_hex ? BigInt(item.best_price_hex) : null,
+    metadata: parsedMeta,
   };
 });
```

This avoids runtime crashes and preserves numeric fidelity.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
return rawData.map(
  (item: {
    token_id: string;
    best_price_hex: string | number | bigint | boolean;
    metadata: string;
  }) => {
    let parsedMeta: RealmMetadata | null = null;
    try {
      parsedMeta = item.metadata ? JSON.parse(item.metadata) : null;
    } catch (_) {
      console.warn("Invalid metadata JSON for token", item.token_id);
    }
    return {
      ...item,
      token_id: BigInt(`0x${item.token_id.split(":")[1] ?? "0"}`),
      best_price_hex: item.best_price_hex ? BigInt(item.best_price_hex) : null,
      metadata: parsedMeta,
    };
  },
);
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-13T04:06:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,12 +158,17 @@ export const RealmDetailModal = ({
 
   // --- Action Handlers ---
   const handleListItem = async () => {
-    if (!contractAddress || !listPrice) return; // Basic validation
+    if (/*!contractAddress || */ !listPrice) return; // Basic validation
     const priceInWei = BigInt(parseFloat(listPrice) * 1e18); // Convert price to wei (assuming 18 decimals for LORDS)
 
+    if (activeMarketOrder?.[0]?.owner != address) {
+      await handleCancelOrder();
+      toast.success("Inactive order cancelled, now listing...");
+    }
+
path: client/apps/landing/src/components/modules/realm-detail-modal.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`BigInt(parseFloat(...)*1e18)` will throw for non-integer floats**

`BigInt()` cannot accept a number with a fractional part.  
If a user enters `"1.23"` this line raises `RangeError: The number 1.23 cannot be converted to a BigInt`.

Use a decimal-to-wei helper (`parseUnits` from viem/ethers) or string math:

```diff
-import { formatUnits } from "viem";
+import { formatUnits, parseUnits } from "viem";

-const priceInWei = BigInt(parseFloat(listPrice) * 1e18);
+const priceInWei = parseUnits(listPrice, 18);   // returns bigint
```

Apply the same fix in `handleEditOrder`.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
-import { formatUnits } from "viem";
+import { formatUnits, parseUnits } from "viem";

    if (/*!contractAddress || */ !listPrice) return; // Basic validation
-    const priceInWei = BigInt(parseFloat(listPrice) * 1e18); // Convert price to wei (assuming 18 decimals for LORDS)
+    const priceInWei = parseUnits(listPrice, 18);   // returns bigint

    if (activeMarketOrder?.[0]?.owner != address) {
      await handleCancelOrder();
      toast.success("Inactive order cancelled, now listing...");
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-13T04:06:03Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-13T04:01:35Z
body: [vc]: #8CWZysxN4DQNODnoLmqD9PAWuw/bOG5u8Apg5vOyb14=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJldGVybnVtIiwicm9vdERpcmVjdG9yeSI6bnVsbCwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6ImV0ZXJudW0tZ2l0LW1hcmtldHBsYWNlLWJ1bGstZnVuY3Rpb25hbGl0eS1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2xvb3QtYmlibGlvdGhlY2EvZXRlcm51bS82Zkt1ZE1ia1lpZkQ0SGZ5NndTcW1DSnhWSlBQIiwicHJldmlld1VybCI6ImV0ZXJudW0tZ2l0LW1hcmtldHBsYWNlLWJ1bGstZnVuY3Rpb25hbGl0eS1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQifSx7Im5hbWUiOiJldGVybnVtLWxhbmRpbmciLCJyb290RGlyZWN0b3J5IjpudWxsLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiZXRlcm51bS1sYW5kaW5nLWdpdC1tYXJrZXRwbGFjZS1idWxrLWZ1LWJmOTJkOS1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2xvb3QtYmlibGlvdGhlY2EvZXRlcm51bS1sYW5kaW5nL0M4YkM1TERCejdpVW1iTHJlaW03bW84UExIcTciLCJwcmV2aWV3VXJsIjoiZXRlcm51bS1sYW5kaW5nLWdpdC1tYXJrZXRwbGFjZS1idWxrLWZ1LWJmOTJkOS1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQifSx7Im5hbWUiOiJldGVybnVtLWRvY3MiLCJyb290RGlyZWN0b3J5IjpudWxsLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiZXRlcm51bS1kb2NzLWdpdC1tYXJrZXRwbGFjZS1idWxrLWZ1bmN0LTBmNDFhMi1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2xvb3QtYmlibGlvdGhlY2EvZXRlcm51bS1kb2NzLzVCNkxER1VSdThXdkNSV0dNaU5wTnpoQ2lzYzIiLCJwcmV2aWV3VXJsIjoiZXRlcm51bS1kb2NzLWdpdC1tYXJrZXRwbGFjZS1idWxrLWZ1bmN0LTBmNDFhMi1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQifSx7Im5hbWUiOiJldGVybnVtLW1vYmlsZSIsInJvb3REaXJlY3RvcnkiOm51bGwsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJldGVybnVtLW1vYmlsZS1naXQtbWFya2V0cGxhY2UtYnVsay1mdW4tYWEwYTgxLWxvb3QtYmlibGlvdGhlY2EudmVyY2VsLmFwcCJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vbG9vdC1iaWJsaW90aGVjYS9ldGVybnVtLW1vYmlsZS9XeW45cTVDakNaQjVEc2dpUllzV3NpbWg0a2VYIiwicHJldmlld1VybCI6ImV0ZXJudW0tbW9iaWxlLWdpdC1tYXJrZXRwbGFjZS1idWxrLWZ1bi1hYTBhODEtbG9vdC1iaWJsaW90aGVjYS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIn0seyJuYW1lIjoiZXRlcm51bS1pbmZyYSIsInJvb3REaXJlY3RvcnkiOm51bGwsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJldGVybnVtLWluZnJhLWdpdC1tYXJrZXRwbGFjZS1idWxrLWZ1bmMtODBmNDE2LWxvb3QtYmlibGlvdGhlY2EudmVyY2VsLmFwcCJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vbG9vdC1iaWJsaW90aGVjYS9ldGVybnVtLWluZnJhL3NSRmZOVW1VN1lSYVo3eWFjZUtSZkY0ZnRicVciLCJwcmV2aWV3VXJsIjoiZXRlcm51bS1pbmZyYS1naXQtbWFya2V0cGxhY2UtYnVsay1mdW5jLTgwZjQxNi1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQifV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **eternum** | ✅ Ready ([Inspect](https://vercel.com/loot-bibliotheca/eternum/6fKudMbkYifD4Hfy6wSqmCJxVJPP)) | [Visit Preview](https://eternum-git-marketplace-bulk-functionality-loot-bibliotheca.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/eternum-git-marketplace-bulk-functionality-loot-bibliotheca.vercel.app?via=pr-comment-feedback-link) | May 13, 2025 4:01am |
| **eternum-docs** | ✅ Ready ([Inspect](https://vercel.com/loot-bibliotheca/eternum-docs/5B6LDGURu8WvCRWGMiNpNzhCisc2)) | [Visit Preview](https://eternum-docs-git-marketplace-bulk-funct-0f41a2-loot-bibliotheca.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/eternum-docs-git-marketplace-bulk-funct-0f41a2-loot-bibliotheca.vercel.app?via=pr-comment-feedback-link) | May 13, 2025 4:01am |
| **eternum-infra** | ✅ Ready ([Inspect](https://vercel.com/loot-bibliotheca/eternum-infra/sRFfNUmU7YRaZ7yaceKRfF4ftbqW)) | [Visit Preview](https://eternum-infra-git-marketplace-bulk-func-80f416-loot-bibliotheca.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/eternum-infra-git-marketplace-bulk-func-80f416-loot-bibliotheca.vercel.app?via=pr-comment-feedback-link) | May 13, 2025 4:01am |
| **eternum-landing** | ✅ Ready ([Inspect](https://vercel.com/loot-bibliotheca/eternum-landing/C8bC5LDBz7iUmbLreim7mo8PLHq7)) | [Visit Preview](https://eternum-landing-git-marketplace-bulk-fu-bf92d9-loot-bibliotheca.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/eternum-landing-git-marketplace-bulk-fu-bf92d9-loot-bibliotheca.vercel.app?via=pr-comment-feedback-link) | May 13, 2025 4:01am |
| **eternum-mobile** | ✅ Ready ([Inspect](https://vercel.com/loot-bibliotheca/eternum-mobile/Wyn9q5CjCZB5DsgiRYsWsimh4keX)) | [Visit Preview](https://eternum-mobile-git-marketplace-bulk-fun-aa0a81-loot-bibliotheca.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/eternum-mobile-git-marketplace-bulk-fun-aa0a81-loot-bibliotheca.vercel.app?via=pr-comment-feedback-link) | May 13, 2025 4:01am |






author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-05-13T04:01:37Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes refactor how NFT metadata is handled throughout the application, shifting from treating metadata as a JSON string to using it as a structured object (`RealmMetadata`). This update affects component props, hooks, type definitions, and data fetching logic, ensuring all relevant code paths expect and process metadata as an object rather than parsing strings.

## Changes

| File(s)                                                                 | Change Summary                                                                                                                                                                                                                                          |
|-------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| .../marketplace-sweep-dialog.tsx<br>.../transfer-season-pass-dialog.tsx<br>.../season-pass-card.tsx<br>.../trade.lazy.tsx | Removed JSON parsing of `metadata`; now uses `metadata` directly as an object. Updated destructuring and attribute access accordingly.                                                                           |
| .../realm-detail-modal.tsx                                              | Changed `realmData` prop type from custom interface to `MergedNftData` with object metadata. Integrated active market order fetching, expiration countdown, and improved UI structure and logic for listings.     |
| .../hooks/services/index.ts                                             | Introduced `ActiveMarketOrder` interface and `fetchActiveMarketOrders` function. Updated related interfaces and functions to use `RealmMetadata` objects for metadata instead of strings.                        |
| .../hooks/useTraitFiltering.ts                                          | Changed hook signature and logic to accept/use `RealmMetadata` objects directly instead of JSON strings, eliminating parsing.                                                                                    |
| .../routes/mint.lazy.tsx                                                | Updated local `getRealmMetadataString` to parse and return a metadata object instead of a string.                                                                                                                |
| .../routes/season-passes.lazy.tsx<br>.../routes/trade.lazy.tsx          | Removed explicit return type from `getSeasonPassMetadataString`; now implicitly returns metadata without parsing.                                                                                                |
| .../types/index.ts                                                      | Changed `MergedNftData.metadata` type from `string | null` to `RealmMetadata | null`.                                                                                                                           |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
  participant UI_Component
  participant Data_Hook
  participant API_Service

  UI_Component->>Data_Hook: Request NFT data
  Data_Hook->>API_Service: Fetch NFT(s) with metadata
  API_Service-->>Data_Hook: Return NFT(s) with metadata as RealmMetadata object
  Data_Hook-->>UI_Component: Provide NFT(s) with structured metadata
  UI_Component->>UI_Component: Access attributes, image, etc. directly from metadata object
```

```mermaid
sequenceDiagram
  participant RealmDetailModal
  participant useQuery (React Query)
  participant API_Service

  RealmDetailModal->>useQuery: Fetch active market orders (by token ID)
  useQuery->>API_Service: Query active orders (token ID, contract address)
  API_Service-->>useQuery: Return ActiveMarketOrder[]
  useQuery-->>RealmDetailModal: Provide active orders and expiration info
  RealmDetailModal->>RealmDetailModal: Display price, expiration countdown, owner, etc.
```

## Poem

> 🐇  
> Hopping through code, I sniffed a change,  
> Metadata’s now clear—no parsing, no strange!  
> Objects in paw, I bound with delight,  
> Traits and images handled just right.  
> Listings and orders, all neat and precise—  
> This bunny approves: the code’s looking nice!  
> 🌱

<!-- walkthrough_end -->
<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->

> [!WARNING]
> There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.
> 
> <details>
> <summary>🔧 ESLint</summary>
> 
> > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
> 
> <details>
> <summary>client/apps/landing/src/components/modules/marketplace-sweep-dialog.tsx</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 9.26.0
> 
> ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
> 
> From ESLint v9.0.0, the default configuration file is now eslint.config.js.
> If you are using a .eslintrc.* file, please follow the migration guide
> to update your configuration file to the new format:
> 
> https://eslint.org/docs/latest/use/configure/migration-guide
> 
> If you still have problems after following the migration guide, please stop by
> https://eslint.org/chat/help to chat with the team.
> 
> 
> </details>
> 
> <details>
> <summary>client/apps/landing/src/components/modules/season-pass-card.tsx</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 9.26.0
> 
> ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
> 
> From ESLint v9.0.0, the default configuration file is now eslint.config.js.
> If you are using a .eslintrc.* file, please follow the migration guide
> to update your configuration file to the new format:
> 
> https://eslint.org/docs/latest/use/configure/migration-guide
> 
> If you still have problems after following the migration guide, please stop by
> https://eslint.org/chat/help to chat with the team.
> 
> 
> </details>
> 
> <details>
> <summary>client/apps/landing/src/routes/mint.lazy.tsx</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 9.26.0
> 
> ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
> 
> From ESLint v9.0.0, the default configuration file is now eslint.config.js.
> If you are using a .eslintrc.* file, please follow the migration guide
> to update your configuration file to the new format:
> 
> https://eslint.org/docs/latest/use/configure/migration-guide
> 
> If you still have problems after following the migration guide, please stop by
> https://eslint.org/chat/help to chat with the team.
> 
> 
> </details>
> 
> + 7 others
> 
> </details>

<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> - CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> 
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=BibliothecaDAO/eternum&utm_content=3181):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-13T04:03:08Z
body: Failed to generate code suggestions for PR
author_association: CONTRIBUTOR