Skip to content

Conversation

@atticusofsparta
Copy link
Contributor

@atticusofsparta atticusofsparta commented Dec 16, 2025

Summary by CodeRabbit

  • New Features
    • Full Marketplace: browse/search listings, listing detail & buy flow, list/upgrade/continue-workflow modals, balances with deposit/withdraw management, marketplace tools, DateTime picker, loading spinner, timeline and interrupted-workflow indicators.
  • Bug Fixes
    • Improved network URL validation and input guards; safer wallet signing; minor UI/UX refinements.
  • Tests
    • Large removal of unit and end-to-end test suites and test configuration.
  • Chores
    • CI workflow/name updates and README package manager command changes; dependency and tooling updates.

✏️ Tip: You can customize this high-level summary in your review settings.

@atticusofsparta atticusofsparta requested a review from a team as a code owner December 16, 2025 15:37
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 16, 2025

📝 Walkthrough

Walkthrough

Adds a Marketplace feature and wiring: new pages, hooks, modals, components, global-state fields, and React Query integrations; threads marketplace identifiers through ArNS flows; removes many Jest/Playwright tests and test bootstraps; updates CI workflows, dependencies, and build tooling.

Changes

Cohort / File(s) Summary
CI & Repo Tooling
​.github/workflows/*, README.md, package.json, biome.json, vite.config.ts, babel.config.cjs
Rename GH Actions jobs/workflows, set concurrency cancel-in-progress, standardize yarn install --frozen-lockfile, README moved examples to pnpm, remove Babel config, update biome ignores/globals and Vite aliases, update package scripts/deps.
Tests & Mocks Removed
jest.config.json, jest-setup.ts, playwright.config.ts, tests/**, __mocks__/react-router-dom.ts, tests/common/mocks/*, src/**/*.test.*
Removed Jest/Playwright configs, global test setup, many unit/e2e tests, fixtures and test mocks.
Global State & Reducer Wiring
src/state/contexts/GlobalState.tsx, src/state/reducers/GlobalReducer.ts
Add marketplaceProcessId, minimumANTVersionForMarketplace, marketplaceContract; initialize default marketplace contract; new reducer actions to set contract, processId, and minimum version; export defaultMarketplaceContract.
ArNS Update & Dispatch Flow
src/state/actions/dispatchArNSUpdate.ts, src/state/actions/dispatchArIOInteraction.ts
Thread marketplaceProcessId into dispatchArNSUpdate; fetch/merge marketplace user assets/domains, include marketplace-owned ANT/domain data; small import tweak in dispatchArIOInteraction.
Routing & App Entry
src/App.tsx, src/utils/routes.tsx, src/components/pages/index.ts
Add /marketplace and /marketplace/names/:name routes; lazy-load Marketplace and ViewListing pages; export ViewListing in pages barrel; set AR SDK log level to debug.
Marketplace Pages & Integrations
src/components/pages/Marketplace/*, src/components/pages/ViewListing/ViewListing.tsx, src/components/pages/Manage/Manage.tsx, src/components/pages/Register/Checkout.tsx
New Marketplace page, ViewListing with buy workflow (deposit + buy), Manage and Checkout flows threaded with marketplaceProcessId, Manage modal integration and balance panels.
Hooks & React Query
src/hooks/useMarketplaceInfo.ts, src/hooks/useMarketplaceOrder.tsx, src/hooks/useMarketplaceOrders.ts, src/hooks/useMarketplaceUserAssets.ts, src/hooks/useANTIntent.ts, src/hooks/useInterruptedWorkflows.ts, src/hooks/index.ts
New React Query builders and hooks: marketplace info, single/paginated orders, user assets, ANT intent detection, interrupted-workflows; re-export added.
Modals & Listing Workflows
src/components/modals/ListNameForSaleModal/*, src/components/modals/ManageMarketplaceARIOModal/*, src/components/modals/ContinueWorkflowModal/*, src/components/modals/ant-management/UpgradeDomainForMarketplaceModal/*, src/components/modals/index.ts
Add multi-panel ListNameForSale modal, ARIO deposit/withdraw modal, ContinueWorkflowModal, UpgradeDomainForMarketplaceModal; exports added to modals index.
Modal Panels & Panels Logic
src/components/modals/ListNameForSaleModal/panels/*, src/components/modals/ManageMarketplaceARIOModal/panels/*
Add FixedPricePanel, ConfirmListingPanel, ProcessTransactionPanel, DepositPanel, WithdrawPanel — include validation, fee calculations, blockchain writes, polling and UX states.
UI Components & Tables
src/components/cards/MarketplaceOrderInfoCard/*, src/components/data-display/tables/MarketplaceListingsTable.tsx, src/components/data-display/tables/DomainsTable.tsx, src/components/indicators/InterruptedWorkflowIndicator/*, src/components/indicators/ARIOLoadingSpinner.tsx, src/components/data-display/VerticalTimelineStepper.tsx, src/components/cards/NavMenuCard/NavMenuCard.tsx, src/components/data-display/tables/TableView.tsx
New MarketplaceOrderInfoCard, MarketplaceListingsTable, InterruptedWorkflowIndicator, ARIOLoadingSpinner, VerticalTimelineStepper; DomainsTable and NavMenuCard extended with marketplace interactions; minor TableView style tweak.
Notifications & UX
src/components/layout/Navbar/NotificationMenu/NotificationMenu.tsx, src/components/layout/Notifications/Notifications.tsx
Notifications memoized and augmented to include interrupted-workflows with ContinueWorkflowModal flow; notification handlers memoized via useCallback.
Settings & DevTools
src/components/pages/Settings/settingsConfig.ts, src/components/pages/Settings/NetworkSettings.tsx, src/components/pages/Settings/devtools/*
Add minimumANTVersionForMarketplace setting and UI, replace URL validation with isValidNetworkURL for network inputs, add ANTTools and MarketplaceTools developer tools.
Utilities & Constants
src/utils/constants.ts, src/utils/searchUtils/searchUtils.ts, src/utils/sentry.ts
Add MARKETPLACE_PROCESS_ID and DEFAULT_MIN_MARKETPLACE_ANT_VERSION; add isValidNetworkURL and expand URL regex; update Sentry router tracing integration.
Wallet Connectors & Small Fixes
src/services/wallets/EthWalletConnector.ts, src/services/wallets/WanderWalletConnector.ts, src/components/inputs/DateTimePicker/DateTimePicker.tsx
Ensure non-empty sign data in Eth connector, add TS casts in Wander connector, and add a DateTimePicker component.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant U as User
    participant V as ViewListingPage
    participant Q as ReactQuery
    participant M as ArNSMarketplaceWrite
    participant AO as AO Network

    U->>V: Open listing page
    V->>Q: fetch order + domain info (useMarketplaceOrder, useDomainInfo)
    Q-->>V: return order, price, domain
    V->>U: render listing

    U->>V: Click "Buy"
    V->>Q: validate wallet & signer
    V->>M: buyFixedPriceANT(antId, price)
    M->>AO: submit transaction
    AO-->>M: confirm tx (txId)
    M-->>V: return success
    V->>Q: invalidate marketplace queries
    V->>U: show success + link
Loading
sequenceDiagram
    autonumber
    participant U as User
    participant D as DomainsTable
    participant L as ListNameForSaleModal
    participant M as ArNSMarketplaceWrite
    participant Q as ReactQuery

    U->>D: Click "List for Sale"
    D->>L: open modal (domain, antId)
    U->>L: configure price & expiration
    U->>L: confirm listing
    L->>M: listNameForSale(antId, config)
    M->>AO: submit transaction
    AO-->>M: confirm tx
    M-->>L: success
    L->>Q: invalidate marketplace queries
    L->>D: emit success
Loading
sequenceDiagram
    autonumber
    participant U as User
    participant N as NotificationMenu
    participant W as ContinueWorkflowModal
    participant M as ArNSMarketplaceWrite
    participant AO as AO Network
    participant Q as ReactQuery

    N->>N: detect interrupted workflows (useInterruptedWorkflows)
    N->>U: show interrupt notification
    U->>N: click "Continue"
    N->>W: open modal with workflow details
    W->>W: analyze workflow type (TRANSFER or PUSH_INTENT)
    U->>W: confirm continue
    W->>M: execute transfer or pushIntent
    M->>AO: submit transaction
    AO-->>M: confirm tx
    M-->>W: success
    W->>Q: refresh queries
    W->>U: show success, close
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • dtfiedler

"A rabbit hopped into the code with a grin,
I threaded marketplaces, modals, and spin.
Tests took a nap, workflows now sing—
Coins and domains bound by a tiny spring.
Hop, click, list — the marketplace wins!" 🐇✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(PE-8734): marketplace' is concise and directly related to the changeset, which introduces comprehensive marketplace functionality including new pages, components, hooks, and state management for domain listing and trading features.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9636ed7 and db3aefc.

📒 Files selected for processing (1)
  • src/components/cards/NavMenuCard/NavMenuCard.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/cards/NavMenuCard/NavMenuCard.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: deploy-preview
  • GitHub Check: lint_and_build

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

Visit the preview URL for this PR (updated for commit 1788b5e):

https://arns-portal--pr903-pe-8734-marketplace-x3nib7bq.web.app

(expires Tue, 30 Dec 2025 15:47:59 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: 1f59769442cb89cf2a92042a342e51fe94047b94

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 16

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/state/contexts/ArNSState.tsx (1)

71-82: Add marketplaceProcessId to the useEffect dependency array.

The marketplaceProcessId is passed to dispatchArNSUpdate on line 77 but is missing from the dependency array on line 82. This violates the React exhaustive-deps rule and will prevent the effect from re-running when marketplaceProcessId changes, potentially causing stale or incorrect ArNS state.

Apply this diff to fix the missing dependency:

-  }, [walletAddress, aoNetwork, arioProcessId, hyperbeamUrl]);
+  }, [walletAddress, aoNetwork, arioProcessId, hyperbeamUrl, marketplaceProcessId, antRegistryProcessId]);

Note: I've also added antRegistryProcessId which appears to have the same issue (used on line 78 but not in dependencies).

src/components/data-display/tables/DomainsTable.tsx (1)

518-524: Missing dependencies in useEffect.

The effect uses marketplaceProcessId, latestAntVersion, and walletAddress inside the callback (lines 479, 472, 462), but these aren't in the dependency array. This could lead to stale table data when these values change.

   }, [
     domainData,
     loading,
     loadingArnsState,
     primaryNameData,
     dispatchArNSState,
+    marketplaceProcessId,
+    latestAntVersion?.moduleId,
+    walletAddress,
   ]);
🟡 Minor comments (13)
src/components/pages/Settings/devtools/ANTTools.tsx-225-229 (1)

225-229: Fix text color for visibility.

The text-black class will render the message invisible against the dark bg-metallic-grey background. Use a visible color like text-grey or text-white for consistency with other status messages.

           {!walletAddress && (
-            <span className="text-black text-xs">
+            <span className="text-grey text-xs">
               Connect wallet to spawn ANT
             </span>
           )}
src/components/inputs/DateTimePicker/DateTimePicker.tsx-21-23 (1)

21-23: Consider initializing time picker to 1 hour from now instead of noon.

The default time value of '12:00' might be in the past if the component is opened later in the day, which could cause confusion when the enforcement logic adjusts it.

Consider initializing to 1 hour from now:

+  const getDefaultTime = () => {
+    const oneHourFromNow = new Date(Date.now() + 60 * 60 * 1000);
+    return format(oneHourFromNow, 'HH:mm');
+  };
+
   const [timeValue, setTimeValue] = useState<string>(
-    value ? format(value, 'HH:mm') : '12:00',
+    value ? format(value, 'HH:mm') : getDefaultTime(),
   );
src/components/cards/MarketplaceOrderInfoCard/MarketplaceOrderInfoCard.tsx-81-81 (1)

81-81: Remove debug console.log before merge.

Debug logging should not be present in production code.

-  console.log(config);
README.md-11-12 (1)

11-12: README uses incorrect package manager commands.

The README (lines 11-12, 18, 24) contains pnpm commands, but the project uses yarn as evidenced by yarn.lock being committed and package.json specifying "packageManager": "yarn@1.22.22". The CI workflows (.github/workflows/pr.yml, .github/workflows/staging_deploy.yml) correctly use yarn install --frozen-lockfile. Update the README to use yarn commands consistently with the project setup.

src/state/actions/dispatchArIOInteraction.ts-23-27 (1)

23-27: Remove unused import NETWORK_DEFAULTS.

The import of NETWORK_DEFAULTS is not referenced anywhere in this file.

Apply this diff:

 import {
   APP_NAME,
-  NETWORK_DEFAULTS,
   WRITE_OPTIONS,
 } from '@src/utils/constants';
src/components/modals/ManageMarketplaceARIOModal/panels/WithdrawPanel.tsx-8-8 (1)

8-8: Unused import.

connect is imported from @permaweb/aoconnect but never used in this file.

-import { connect } from '@permaweb/aoconnect';
src/hooks/useMarketplaceUserAssets.ts-19-25 (1)

19-25: Query key contains an object which may cause cache misses.

aoNetwork.ARIO is included in the query key, but it's an object. React Query uses referential equality for objects in query keys. If aoNetwork.ARIO is recreated on each render (even with the same values), it will cause unnecessary cache invalidations and refetches.

Consider using a stable identifier instead:

     queryKey: [
       'marketplace-user-assets',
       address,
       marketplaceProcessId,
-      aoNetwork.ARIO,
+      aoNetwork.ARIO.CU_URL, // or another stable primitive identifier
     ],
src/components/modals/ContinueWorkflowModal/ContinueWorkflowModal.tsx-349-352 (1)

349-352: Success message text is incorrect for PUSH_INTENT workflow type.

The success message always states "The ANT has been transferred to the marketplace with the correct intent ID", but for PUSH_INTENT, the action is pushing an intent resolution, not transferring the ANT. The correct message is computed at line 227-230 but not used in this success state display.

Consider using a dynamic message or differentiating the displayed text based on the workflow type.

src/components/data-display/tables/MarketplaceListingsTable.tsx-338-340 (1)

338-340: Missing dependency antToNameMap in columns useMemo.

The columns definition uses antToNameMap at line 166, but it's not included in the dependency array. This could lead to stale data being displayed when ArNS records update.

     ],
-    [arioTicker],
+    [arioTicker, antToNameMap],
   );
src/components/pages/ViewListing/ViewListing.tsx-61-128 (1)

61-128: Missing validation: user should not be able to buy their own listing.

The handleBuy function validates wallet and order data but doesn't check if the buyer is the seller. This could lead to a confusing UX or unexpected behavior if a user attempts to purchase their own listing.

Consider adding a check:

       if (!name) {
         throw new Error('Domain name not found');
       }
+      if (orderData.creator === walletAddress.toString()) {
+        throw new Error('Cannot purchase your own listing');
+      }
       const marketplaceContract = new ArNSMarketplaceWrite({
src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx-52-54 (1)

52-54: Misleading comment.

The comment says "Default to 1 hour from now (current day)" but the expiration date panel (FixedPricePanel line 247) enforces a minimum of 1 day from now. This default will fail validation when the user clicks Next without changing the date.

   const [expirationDate, setExpirationDate] = useState<Date | undefined>(
-    new Date(Date.now() + 60 * 60 * 1000), // Default to 1 hour from now (current day)
+    new Date(Date.now() + 24 * 60 * 60 * 1000), // Default to 1 day from now (matches minDate)
   );
src/components/data-display/tables/DomainsTable.tsx-206-252 (1)

206-252: Missing loading state handling in MarketplaceActionIcon.

The component doesn't handle the loading state from useMarketplaceOrder. When order is undefined but still loading, the logic falls through to the error state tooltip (line 221-223), which could show a false error flash while data is being fetched.

 function MarketplaceActionIcon({
   domainName,
   processId,
 }: { domainName: string; processId: string }) {
   const { hasIntent } = useANTIntent(processId);

-  const { data: order, error: orderError } = useMarketplaceOrder({
+  const { data: order, error: orderError, isLoading: orderLoading } = useMarketplaceOrder({
     antId: processId,
   });

+  // Show loading state while fetching order
+  if (orderLoading) {
+    return <Activity className="w-[18px] h-[18px] text-grey animate-pulse" />;
+  }
+
   // If there's an intent but no order (or order fetch failed), show Activity icon
   const hasOrder = order && !orderError;
src/components/modals/ListNameForSaleModal/panels/ConfirmListingPanel.tsx-117-134 (1)

117-134: Potential null safety issue with walletAddress.

If walletAddress is undefined, the 'Current Owner' field will display "undefined...undefined". Add a fallback or guard.

   const orderSummary: Record<string, ReactNode> = {
     'Listing Type': getListingTypeLabel(),
     'Domain Name': domainName,
     'Current Owner':
-      walletAddress?.toString().slice(0, 8) +
-      '...' +
-      walletAddress?.toString().slice(-8),
+      walletAddress
+        ? `${walletAddress.toString().slice(0, 8)}...${walletAddress.toString().slice(-8)}`
+        : 'Unknown',
     Expires: expirationDate
🧹 Nitpick comments (37)
src/components/pages/Settings/devtools/ANTTools.tsx (3)

31-36: Consider removing antState from dependencies to allow manual clearing.

Including antState in the dependency array means if a user clears the textarea, it will immediately reset to the default state. If intentional for devtools convenience, consider adding a "Reset to Default" button instead for clearer UX.

   React.useEffect(() => {
-    if (walletAddress && !antState) {
+    if (walletAddress) {
       const defaultState = createAntStateForOwner(walletAddress.toString());
       setAntState(JSON.stringify(defaultState, null, 2));
     }
-  }, [walletAddress, antState]);
+  }, [walletAddress]);

43-47: Remove unreachable code.

The check at lines 45-47 is unreachable because canSpawn (line 44) already includes wallet?.contractSigner in its condition. If canSpawn is false, the function returns early; if true, wallet.contractSigner is guaranteed to exist.

   async function handleSpawnANT() {
     if (!canSpawn) return;
-    if (!wallet?.contractSigner) {
-      throw new Error('No Wander Signer found');
-    }

     setIsSpawning(true);

177-183: Consider adding real-time JSON validation.

Unlike the Module ID input which shows immediate validation feedback, the JSON textarea only validates on spawn attempt. For devtools UX, consider adding a visual indicator (border color change or error message) when JSON is malformed.

src/components/inputs/DateTimePicker/DateTimePicker.tsx (6)

6-12: Export the DateTimePickerProps interface for reusability.

The interface is not exported, which limits its reusability in other parts of the codebase that might need to reference these prop types.

Apply this diff:

-interface DateTimePickerProps {
+export interface DateTimePickerProps {
   value?: Date;
   onChange: (date: Date | undefined) => void;
   placeholder?: string;
   minDate?: Date;
   className?: string;
 }

77-89: Consider the implications of returning false when selectedDate is undefined.

When no date is selected, isTimeDisabled returns false for all times, meaning all time options appear enabled. This is somewhat inconsistent with the component's behavior, though in practice the time picker is only used after a date is selected.

Consider adding a comment to clarify this design decision:

   const isTimeDisabled = (
     hours: number,
     minutes: number,
     selectedDate?: Date,
   ) => {
+    // When no date is selected, allow all times (will be validated on date selection)
     if (!selectedDate) return false;
 
     const testDateTime = new Date(selectedDate);
     testDateTime.setHours(hours, minutes, 0, 0);
 
     const oneHourFromNow = new Date(Date.now() + 60 * 60 * 1000);
     return testDateTime < oneHourFromNow;
   };

92-197: Move Calendar component outside to prevent recreation on every render.

The Calendar component is defined inside DateTimePicker, causing it to be recreated on every render. This is inefficient and can impact performance.

Move the Calendar component outside the DateTimePicker function:

+// Calendar component for date selection
+const Calendar = ({
+  selected,
+  onSelect,
+  isDateDisabled,
+}: {
+  selected?: Date;
+  onSelect: (date: Date | undefined) => void;
+  isDateDisabled: (date: Date) => boolean;
+}) => {
+  const [currentMonth, setCurrentMonth] = useState(selected || new Date());
+  // ... rest of Calendar implementation
+};
+
 function DateTimePicker({
   value,
   onChange,
   placeholder = 'Select date and time',
   className = '',
 }: DateTimePickerProps) {
   // ... component implementation
-
-  // Simple calendar component
-  const Calendar = ({
-    selected,
-    onSelect,
-  }: { selected?: Date; onSelect: (date: Date | undefined) => void }) => {
-    // ... Calendar implementation
-  };

Note: You'll need to pass isDateDisabled as a prop to the Calendar component.


152-156: Consider constraining calendar navigation to valid date range.

The calendar allows navigating to any month, but dates are only valid for today to 30 days from now. Users might navigate to months where all dates are disabled, which could be confusing.

Add constraints to the navigation:

   const navigateMonth = (direction: 'prev' | 'next') => {
     const newDate = new Date(currentMonth);
     newDate.setMonth(newDate.getMonth() + (direction === 'next' ? 1 : -1));
-    setCurrentMonth(newDate);
+    
+    // Prevent navigating to months outside the valid range
+    const today = new Date();
+    const maxDate = new Date();
+    maxDate.setDate(maxDate.getDate() + 30);
+    
+    if (newDate >= today && newDate <= maxDate) {
+      setCurrentMonth(newDate);
+    }
   };

162-178: Improve accessibility with ARIA attributes and labels.

The calendar navigation buttons use arrow symbols without ARIA labels, which may not be clear for screen reader users.

Add ARIA attributes to improve accessibility:

         <button
           type="button"
           className="p-1 hover:bg-grey rounded text-white"
           onClick={() => navigateMonth('prev')}
+          aria-label="Previous month"
         >
           ←
         </button>
         <h3 className="text-white font-medium">
           {format(currentMonth, 'MMMM yyyy')}
         </h3>
         <button
           type="button"
           className="p-1 hover:bg-grey rounded text-white"
           onClick={() => navigateMonth('next')}
+          aria-label="Next month"
         >
           →
         </button>

Similarly, consider adding aria-label to the hour and minute selects at Lines 239 and 268.


227-227: Reconsider the extremely high z-index value.

The z-index of 99999 is very high and might cause stacking context issues or conflicts with other UI elements (e.g., modals, tooltips).

Consider using a more reasonable z-index value that fits within your application's z-index scale:

         <Popover.Content
           className="w-80 rounded-lg border border-dark-grey bg-metallic-grey shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
-          style={{ zIndex: 99999 }}
+          style={{ zIndex: 1000 }}
           sideOffset={8}
           align="start"
         >

If this high value is necessary due to other elements in your application, consider defining z-index values in a centralized constants file.

src/components/modals/ant-management/UpgradeDomainForMarketplaceModal/UpgradeDomainForMarketplaceModal.tsx (1)

65-138: Consider improving error handling in upgradeDomain.

The .catch() on line 99-101 swallows the error and only pushes to failedUpgrades. This means the await resolves successfully even when the upgrade fails, which could be confusing. Also, failedUpgrades is an array but only ever contains at most one element.

Consider either:

  1. Using a boolean flag instead of an array
  2. Re-throwing after recording the failure, or using try/catch instead
-      const failedUpgrades = [];
+      let upgradeFailed = false;

       await dispatchANTInteraction({
         // ... payload
       }).catch(() => {
-        failedUpgrades.push(processId);
+        upgradeFailed = true;
       });

-      if (failedUpgrades.length) {
+      if (upgradeFailed) {
         eventEmitter.emit('error', {
src/components/indicators/InterruptedWorkflowIndicator/InterruptedWorkflowIndicator.css (1)

11-14: Move transition property to base class for smooth hover-out animation.

The transition property is currently only applied during hover, which means the element will snap back instantly when hover ends. Moving it to the base .interrupted-workflow-indicator class ensures smooth transitions in both directions.

Apply this diff:

 .interrupted-workflow-indicator {
   position: relative;
   display: inline-flex;
   align-items: center;
   justify-content: center;
   cursor: pointer;
+  transition: transform 0.2s ease-in-out;
 }
 
 .interrupted-workflow-indicator:hover {
   transform: scale(1.1);
-  transition: transform 0.2s ease-in-out;
 }
src/components/modals/ManageMarketplaceARIOModal/ManageMarketplaceARIOModal.tsx (2)

29-31: Add accessibility label to the close button.

The close button should have an accessible label for screen readers.

-          <button onClick={onClose}>
+          <button onClick={onClose} aria-label="Close modal">
             <XIcon className="size-5" />
           </button>

37-59: Consider using Tabs.Trigger for better accessibility.

The custom tab buttons work, but using Radix UI's Tabs.Trigger would provide built-in keyboard navigation and ARIA attributes. The current implementation duplicates tab state management.

If keeping custom buttons, add ARIA attributes:

             <button
+              role="tab"
+              aria-selected={activeTab === 'deposit'}
               className={`flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-all ${
                 activeTab === 'deposit'
                   ? 'bg-white text-black'
                   : 'text-grey hover:text-white'
               }`}
               onClick={() => setActiveTab('deposit')}
             >
src/components/indicators/InterruptedWorkflowIndicator/InterruptedWorkflowIndicator.tsx (1)

24-30: Consider extracting tooltip style colors to avoid duplication.

The tooltip style colors here (#fef2f2, #fecaca, #991b1b) duplicate values from RedPingAnimation.css (lines 67-69). Consider using CSS classes instead of inline styles for consistency and easier maintenance.

     <Tooltip
-      tooltipOverrides={{
-        overlayInnerStyle: {
-          backgroundColor: '#fef2f2',
-          border: '1px solid #fecaca',
-          color: '#991b1b',
-        },
-      }}
+      tooltipOverrides={{
+        overlayClassName: 'interrupted-workflow-tooltip',
+      }}
       message={
src/components/pages/Settings/devtools/MarketplaceTools.tsx (2)

33-39: Redundant state update in handleReset.

Line 38 sets newVersion manually, but the useEffect on lines 19-21 will sync it automatically when minimumANTVersionForMarketplace changes. This redundancy is harmless but unnecessary.

   const handleReset = () => {
     dispatchGlobalState({
       type: 'setMinimumANTVersionForMarketplace',
       payload: DEFAULT_MIN_MARKETPLACE_ANT_VERSION,
     });
-    setNewVersion(DEFAULT_MIN_MARKETPLACE_ANT_VERSION.toString());
   };

23-31: Input validation accepts decimal values that get truncated.

parseInt will truncate decimal inputs (e.g., "25.7" becomes 25). Consider using a stricter validation or informing users that only integers are accepted.

   const handleVersionUpdate = () => {
     const version = parseInt(newVersion, 10);
-    if (!isNaN(version) && version > 0) {
+    if (!isNaN(version) && version > 0 && Number.isInteger(Number(newVersion))) {
       dispatchGlobalState({
         type: 'setMinimumANTVersionForMarketplace',
         payload: version,
       });
+    } else {
+      // Optionally show validation error to user
     }
   };
src/components/modals/ManageMarketplaceARIOModal/panels/WithdrawPanel.tsx (3)

273-285: Silent input clamping may confuse users.

When the user types a value exceeding the balance, line 278 silently clamps it to currentBalance. This could be unexpected - users might not realize their input was modified. Consider showing a visual indication or allowing the invalid input with an error message.

             onChange={(e) => {
               const val = e.target.value;

               if (isValidCustomFormat(val)) {
                 const numVal = Number(val);
-                const clampedVal = Math.min(numVal, currentBalance);
-                setCustomValue(clampedVal.toString());
+                setCustomValue(val);
                 setPercentageSelected(undefined);
                 setCustomValueError(
-                  isValidCustomAmount(clampedVal.toString()) || '',
+                  isValidCustomAmount(val) || '',
                 );
                 setError('');
               }
             }}

145-147: Consider simplifying the amount conversion.

amountInMARIO.valueOf().toString() could potentially be simplified if the SDK accepts the mARIO type directly, though this depends on the SDK's API.

#!/bin/bash
# Check the withdrawArIO method signature in the SDK
rg -n "withdrawArIO" --type=ts -C 3 node_modules/@ar.io/sdk || echo "SDK not in node_modules, check docs"
@ar.io/sdk ArNSMarketplaceWrite withdrawArIO method signature

149-157: Hardcoded sleep duration for blockchain confirmation.

The 3-second sleep is a magic number and may not be sufficient for all network conditions. Consider making this configurable or adding a comment explaining the rationale.

-      // Wait a bit for cranking to complete
-      await sleep(3000);
+      // Wait for the AO network to process the withdrawal (cranking)
+      // 3 seconds is typically sufficient for normal network conditions
+      const WITHDRAWAL_CONFIRMATION_DELAY_MS = 3000;
+      await sleep(WITHDRAWAL_CONFIRMATION_DELAY_MS);
src/components/pages/ViewListing/ViewListing.tsx (3)

259-269: Potential runtime error if domainInfo?.processId is undefined.

ArweaveTransactionID constructor throws an error if passed an invalid or undefined value. While domainInfo should exist at this point due to the earlier guard (line 131), accessing processId directly without a fallback could cause issues if the component re-renders in an unexpected state.

Consider a defensive check or ensure the guard covers this case:

-                  <ArweaveID
-                    id={new ArweaveTransactionID(domainInfo?.processId)}
+                  <ArweaveID
+                    id={new ArweaveTransactionID(domainInfo?.processId ?? '')}

Alternatively, wrap this section with {domainInfo?.processId && (...)}.


274-282: Type assertion as any bypasses type safety.

The cast (domainInfo.arnsRecord as any)?.endTimestamp suggests a type mismatch. This could hide issues if the record structure changes.

Consider defining a proper type guard or extending the type definition to include endTimestamp for lease records:

// Define a type guard or use conditional typing
const leaseEndTimestamp = domainInfo?.arnsRecord?.type === 'lease' 
  ? (domainInfo.arnsRecord as { endTimestamp?: number }).endTimestamp 
  : undefined;

386-406: Buy button is always enabled when not processing, even without a connected wallet.

The button doesn't visually indicate that a wallet connection is required. While handleBuy will throw an error, a better UX would disable the button or show a connect wallet prompt.

                 <button
                   className={`w-full font-semibold py-3 px-6 rounded transition-colors flex items-center justify-center gap-2 mt-8 ${
-                    isBuying
-                      ? 'bg-grey text-white cursor-not-allowed'
-                      : 'bg-primary hover:bg-warning text-black'
+                    isBuying || !wallet?.contractSigner
+                      ? 'bg-grey text-white cursor-not-allowed'
+                      : 'bg-primary hover:bg-warning text-black'
                   }`}
                   onClick={handleBuy}
-                  disabled={isBuying}
+                  disabled={isBuying || !wallet?.contractSigner}
                 >
                   {isBuying ? (
                     <>
                       <Loader size={20} />
                       Processing...
                     </>
+                  ) : !wallet?.contractSigner ? (
+                    <>Connect Wallet to Buy</>
                   ) : (
                     <>
                       <ShoppingCart className="w-5 h-5" />
                       Buy Now
                     </>
                   )}
                 </button>
src/components/modals/ContinueWorkflowModal/ContinueWorkflowModal.tsx (2)

8-8: Unused import: connect from @permaweb/aoconnect.

The connect function is imported but never used in this file.

-import { connect } from '@permaweb/aoconnect';

187-188: Type assertion as any bypasses type safety for arioContract.

This cast appears in multiple places in the PR. Consider creating a properly typed interface or fixing the underlying type mismatch.

src/components/data-display/tables/MarketplaceListingsTable.tsx (1)

101-105: Early return may cause unnecessary re-renders or flash of empty state.

Returning an empty array when arnsRecords is falsy happens after ordersData is checked. If arnsRecords takes longer to load, users might see an empty table briefly even when orders exist. Consider combining this check with the loading state or showing a more informative message.

src/hooks/useANTIntent.ts (2)

3-3: Unused import: useDomainInfo.

This import is declared but never used in the hook.

-import useDomainInfo from './useDomainInfo';

48-63: isInterruptedWorkflow and hasIntent are semantically identical.

Both values are derived from !!antIntent, making them redundant. The comment at lines 48-50 suggests that any existing intent indicates an interrupted workflow, but this naming may be misleading since not all intents represent interrupted states (some might be in-progress but not interrupted).

If the intent is that "interrupted" means the intent exists but wasn't processed, consider adding more specific logic based on antIntent.status to differentiate active vs interrupted intents.

   // Check if this is an interrupted workflow
-  // An interrupted workflow occurs when:
-  // There's an intent for the ANT
-  const isInterruptedWorkflow = !!antIntent;
+  // An interrupted workflow occurs when the intent exists but is in a pending/failed state
+  const isInterruptedWorkflow = antIntent && antIntent.status !== 'completed';

If the current behavior is intentional, consider removing one of the redundant fields or documenting why both are needed.

src/components/layout/Navbar/NotificationMenu/NotificationMenu.tsx (1)

159-163: Optional: trivial memoization.

Memoizing interruptedWorkflows.length provides minimal benefit since array length access is O(1). You could simply use interruptedWorkflows.length directly in the notifications useMemo.

src/components/modals/ListNameForSaleModal/panels/FixedPricePanel.tsx (1)

259-264: Unexpected navigation: window.history.back() in modal.

Using window.history.back() for the Cancel button may cause unexpected navigation if the user opened this modal from a different route or via deep linking. Consider adding an onCancel prop callback to allow the parent to control this behavior.

src/components/data-display/tables/DomainsTable.tsx (2)

288-371: Optional: extract shared ContinueWorkflowModal logic.

Both RoleDisplay and InterruptedWorkflowAction components manage their own showContinueWorkflowModal state and render ContinueWorkflowModal. Consider extracting a shared hook or wrapper component to reduce duplication.


869-938: Consider extracting IIFE to a named component.

The inline IIFE for marketplace action logic (lines 869-938) is quite complex. Extracting this to a named component like <MarketplaceListingAction domainName={...} processId={...} ... /> would improve readability and testability.

src/hooks/useMarketplaceOrders.ts (2)

59-64: totalCount may be misleading.

totalCount: allOrders.length returns the number of fetched items, not the actual total count of orders in the marketplace. If the API provides a total count, it should be surfaced; otherwise, consider renaming to fetchedCount to avoid confusion.

       return {
         items: allOrders,
         hasMore,
         nextCursor: cursor,
-        totalCount: allOrders.length,
+        fetchedCount: allOrders.length,
+        // If the API provides totalCount, add: totalCount: result.totalCount,
       };

36-44: Pagination logic could be clearer.

The limit calculation Math.min(limit, (filters?.limit || limit) - allOrders.length) is complex. Since the while condition already guards against exceeding filters.limit, simplifying to just limit for the per-page size would be clearer, with the outer loop handling the total constraint.

-      while (hasMore && (!filters?.limit || allOrders.length < filters.limit)) {
-        const result: any = await marketplaceContract.getPaginatedOrders({
-          cursor,
-          limit: Math.min(limit, (filters?.limit || limit) - allOrders.length),
+      const targetLimit = filters?.limit ?? Infinity;
+      while (hasMore && allOrders.length < targetLimit) {
+        const remaining = targetLimit - allOrders.length;
+        const result: any = await marketplaceContract.getPaginatedOrders({
+          cursor,
+          limit: Math.min(limit, remaining),
src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx (1)

122-136: Query reset predicates may be overly broad.

The predicate key.startsWith('marketplace') could reset unrelated queries if other features use similar prefixes. Consider using exact query key matching or a more specific pattern.

src/state/actions/dispatchArNSUpdate.ts (1)

124-124: Remove debug logging before merge.

This console.log appears to be debug output that should be removed or converted to conditional debug logging before merging to develop.

-    console.log('marketplaceUserDomains', userDomains);
src/components/modals/ManageMarketplaceARIOModal/panels/DepositPanel.tsx (1)

298-311: Input clamping may silently truncate user input.

When the user enters a value greater than MAX_ARIO_DEPOSIT, the input is silently clamped to the max. Consider showing immediate feedback or preventing the input rather than auto-clamping, which could confuse users.

 onChange={(e) => {
   const val = e.target.value;

   if (isValidCustomFormat(val)) {
     const numVal = Number(val);
-    const clampedVal = Math.min(numVal, MAX_ARIO_DEPOSIT);
-    setCustomValue(clampedVal.toString());
+    setCustomValue(val);
     setButtonSelected(undefined);
     setCustomValueError(
-      isValidCustomAmount(clampedVal.toString()) || '',
+      isValidCustomAmount(val) || '',
     );
     setError('');
   }
 }}
src/hooks/useInterruptedWorkflows.ts (1)

15-22: Consider stronger typing for domains parameter.

The domains: Record<string, any> parameter could be more strictly typed to match the actual domain record structure (e.g., Record<string, AoArNSNameData>), improving type safety and IDE support.

+import { AoArNSNameData } from '@ar.io/sdk';
+
 export function useInterruptedWorkflows(
   ants: Record<string, ANTProcessData>,
-  domains: Record<string, any>,
+  domains: Record<string, AoArNSNameData>,
 ): {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d68c44a and 1788b5e.

⛔ Files ignored due to path filters (2)
  • src/components/inputs/text/ValidationInput/__snapshots__/ValidationInput.test.tsx.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (76)
  • .github/workflows/build_and_test.yml (2 hunks)
  • .github/workflows/pr.yml (1 hunks)
  • .github/workflows/production.yml (2 hunks)
  • .github/workflows/staging_deploy.yml (1 hunks)
  • README.md (1 hunks)
  • __mocks__/react-router-dom.ts (0 hunks)
  • babel.config.cjs (0 hunks)
  • biome.json (2 hunks)
  • jest-setup.ts (0 hunks)
  • jest.config.json (0 hunks)
  • package.json (3 hunks)
  • playwright.config.ts (0 hunks)
  • src/App.tsx (3 hunks)
  • src/components/animations/RedPingAnimation.css (1 hunks)
  • src/components/cards/MarketplaceOrderInfoCard/MarketplaceOrderInfoCard.tsx (1 hunks)
  • src/components/cards/MarketplaceOrderInfoCard/index.ts (1 hunks)
  • src/components/cards/NavMenuCard/NavMenuCard.tsx (7 hunks)
  • src/components/data-display/tables/DomainsTable.tsx (9 hunks)
  • src/components/data-display/tables/MarketplaceListingsTable.tsx (1 hunks)
  • src/components/indicators/InterruptedWorkflowIndicator/InterruptedWorkflowIndicator.css (1 hunks)
  • src/components/indicators/InterruptedWorkflowIndicator/InterruptedWorkflowIndicator.tsx (1 hunks)
  • src/components/inputs/DateTimePicker/DateTimePicker.tsx (1 hunks)
  • src/components/inputs/buttons/ConnectButton/ConnectButton.test.tsx (0 hunks)
  • src/components/inputs/text/ValidationInput/ValidationInput.test.tsx (0 hunks)
  • src/components/layout/Navbar/NavBarLink/NavBarLink.test.tsx (0 hunks)
  • src/components/layout/Navbar/NavGroup/NavGroup.test.tsx (0 hunks)
  • src/components/layout/Navbar/NotificationMenu/NotificationMenu.tsx (2 hunks)
  • src/components/layout/Notifications/Notifications.tsx (3 hunks)
  • src/components/modals/ContinueWorkflowModal/ContinueWorkflowModal.tsx (1 hunks)
  • src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx (1 hunks)
  • src/components/modals/ListNameForSaleModal/panels/ConfirmListingPanel.tsx (1 hunks)
  • src/components/modals/ListNameForSaleModal/panels/FixedPricePanel.tsx (1 hunks)
  • src/components/modals/LogoUploadModal/__tests__/LogoUploadModal.test.tsx (0 hunks)
  • src/components/modals/ManageMarketplaceARIOModal/ManageMarketplaceARIOModal.tsx (1 hunks)
  • src/components/modals/ManageMarketplaceARIOModal/index.ts (1 hunks)
  • src/components/modals/ManageMarketplaceARIOModal/panels/DepositPanel.tsx (1 hunks)
  • src/components/modals/ManageMarketplaceARIOModal/panels/WithdrawPanel.tsx (1 hunks)
  • src/components/modals/ant-management/UpgradeDomainForMarketplaceModal/UpgradeDomainForMarketplaceModal.tsx (1 hunks)
  • src/components/modals/ant-management/UpgradeDomainForMarketplaceModal/index.ts (1 hunks)
  • src/components/modals/ant-management/UpgradeDomainForMarketplaceModal/styles.css (1 hunks)
  • src/components/modals/index.ts (1 hunks)
  • src/components/pages/Manage/Manage.tsx (2 hunks)
  • src/components/pages/Marketplace/Marketplace.tsx (1 hunks)
  • src/components/pages/Register/Checkout.tsx (2 hunks)
  • src/components/pages/Settings/NetworkSettings.tsx (7 hunks)
  • src/components/pages/Settings/devtools/ANTTools.tsx (2 hunks)
  • src/components/pages/Settings/devtools/DevTools.tsx (2 hunks)
  • src/components/pages/Settings/devtools/MarketplaceTools.tsx (1 hunks)
  • src/components/pages/Settings/settingsConfig.ts (2 hunks)
  • src/components/pages/ViewListing/ViewListing.tsx (1 hunks)
  • src/components/pages/index.ts (2 hunks)
  • src/hooks/index.ts (1 hunks)
  • src/hooks/useANTIntent.ts (1 hunks)
  • src/hooks/useInterruptedWorkflows.ts (1 hunks)
  • src/hooks/useMarketplaceInfo.ts (1 hunks)
  • src/hooks/useMarketplaceOrder.tsx (1 hunks)
  • src/hooks/useMarketplaceOrders.ts (1 hunks)
  • src/hooks/useMarketplaceUserAssets.ts (1 hunks)
  • src/state/actions/dispatchArIOInteraction.ts (2 hunks)
  • src/state/actions/dispatchArNSUpdate.ts (6 hunks)
  • src/state/contexts/ArNSState.tsx (2 hunks)
  • src/state/contexts/GlobalState.tsx (8 hunks)
  • src/state/reducers/GlobalReducer.ts (3 hunks)
  • src/utils/constants.ts (2 hunks)
  • src/utils/imageUtils.test.ts (0 hunks)
  • src/utils/routes.tsx (3 hunks)
  • src/utils/searchUtils/searchUtils.test.ts (0 hunks)
  • src/utils/searchUtils/searchUtils.ts (1 hunks)
  • src/utils/sentry.ts (1 hunks)
  • tests/common/fixtures/TestRecords.ts (0 hunks)
  • tests/common/mocks/ArweaveCompositeDataProviderMock.ts (0 hunks)
  • tests/common/mocks/svg.js (0 hunks)
  • tests/playwright/mobile.spec.ts (0 hunks)
  • tests/playwright/setup.ts (0 hunks)
  • tests/unit/HomeSearch.test.tsx (0 hunks)
  • vite.config.ts (0 hunks)
💤 Files with no reviewable changes (19)
  • tests/common/mocks/svg.js
  • vite.config.ts
  • tests/common/fixtures/TestRecords.ts
  • playwright.config.ts
  • tests/playwright/mobile.spec.ts
  • src/components/modals/LogoUploadModal/tests/LogoUploadModal.test.tsx
  • mocks/react-router-dom.ts
  • babel.config.cjs
  • src/components/inputs/text/ValidationInput/ValidationInput.test.tsx
  • src/components/layout/Navbar/NavGroup/NavGroup.test.tsx
  • src/utils/searchUtils/searchUtils.test.ts
  • src/utils/imageUtils.test.ts
  • tests/unit/HomeSearch.test.tsx
  • jest.config.json
  • src/components/layout/Navbar/NavBarLink/NavBarLink.test.tsx
  • tests/common/mocks/ArweaveCompositeDataProviderMock.ts
  • src/components/inputs/buttons/ConnectButton/ConnectButton.test.tsx
  • tests/playwright/setup.ts
  • jest-setup.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-12-11T05:20:18.825Z
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 902
File: src/components/modals/turbo/panels/crypto/CryptoConfirmation.tsx:536-540
Timestamp: 2025-12-11T05:20:18.825Z
Learning: Guideline: For tokens on Base network that use 6 decimals (e.g., base-ario ERC-20), convert amount to smallest units using a 1e6 multiplier. Use Math.round(amount * 1e6) when computing the on-chain value. Do not assume 18 decimals. Apply this conversion consistently in code paths that perform payments in CryptoConfirmation.tsx or similar components, and document that the base-ario/USDC-like tokens use 6 decimals to avoid mispricing or transfer errors.

Applied to files:

  • src/components/pages/Settings/devtools/MarketplaceTools.tsx
  • src/components/pages/Manage/Manage.tsx
  • src/components/pages/Register/Checkout.tsx
  • src/components/modals/ContinueWorkflowModal/ContinueWorkflowModal.tsx
  • src/components/modals/ant-management/UpgradeDomainForMarketplaceModal/UpgradeDomainForMarketplaceModal.tsx
  • src/components/layout/Notifications/Notifications.tsx
  • src/components/pages/ViewListing/ViewListing.tsx
  • src/components/modals/ManageMarketplaceARIOModal/panels/WithdrawPanel.tsx
  • src/components/data-display/tables/DomainsTable.tsx
  • src/components/pages/Settings/devtools/DevTools.tsx
  • src/components/pages/Settings/devtools/ANTTools.tsx
  • src/components/data-display/tables/MarketplaceListingsTable.tsx
  • src/components/modals/ListNameForSaleModal/panels/FixedPricePanel.tsx
  • src/components/inputs/DateTimePicker/DateTimePicker.tsx
  • src/components/modals/ManageMarketplaceARIOModal/ManageMarketplaceARIOModal.tsx
  • src/components/cards/NavMenuCard/NavMenuCard.tsx
  • src/components/pages/Marketplace/Marketplace.tsx
  • src/components/modals/ManageMarketplaceARIOModal/panels/DepositPanel.tsx
  • src/components/cards/MarketplaceOrderInfoCard/MarketplaceOrderInfoCard.tsx
  • src/components/indicators/InterruptedWorkflowIndicator/InterruptedWorkflowIndicator.tsx
  • src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx
  • src/components/modals/ListNameForSaleModal/panels/ConfirmListingPanel.tsx
  • src/components/pages/Settings/NetworkSettings.tsx
  • src/components/layout/Navbar/NotificationMenu/NotificationMenu.tsx
📚 Learning: 2025-09-10T16:35:55.727Z
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 873
File: src/components/modals/ant-management/UpgradeDomainsModal/UpgradeDomainsModal.tsx:0-0
Timestamp: 2025-09-10T16:35:55.727Z
Learning: In arns-react, when calling buildDomainInfoQuery (src/hooks/useDomainInfo.tsx), you must pass arioProcessId along with arioContract, domain, aoNetwork, hyperbeamUrl, and antRegistryProcessId; omitting arioProcessId can lead to "No ANT id or record found" and break upgrades.

Applied to files:

  • src/components/pages/Manage/Manage.tsx
  • src/components/pages/Register/Checkout.tsx
  • src/components/modals/ant-management/UpgradeDomainForMarketplaceModal/UpgradeDomainForMarketplaceModal.tsx
  • src/components/data-display/tables/DomainsTable.tsx
  • src/state/contexts/ArNSState.tsx
  • src/state/actions/dispatchArNSUpdate.ts
  • src/hooks/useMarketplaceOrder.tsx
  • src/components/pages/Settings/settingsConfig.ts
  • src/state/contexts/GlobalState.tsx
📚 Learning: 2025-09-10T16:35:55.727Z
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 873
File: src/components/modals/ant-management/UpgradeDomainsModal/UpgradeDomainsModal.tsx:0-0
Timestamp: 2025-09-10T16:35:55.727Z
Learning: In arns-react, when calling buildDomainInfoQuery (src/hooks/useDomainInfo.tsx), you must pass arioProcessId along with arioContract, domain, aoNetwork, hyperbeamUrl, and antRegistryProcessId; omitting arioProcessId can lead to “No ANT id or record found” and break upgrades.

Applied to files:

  • src/components/pages/Manage/Manage.tsx
  • src/components/pages/Register/Checkout.tsx
  • src/components/modals/ant-management/UpgradeDomainForMarketplaceModal/UpgradeDomainForMarketplaceModal.tsx
  • src/components/data-display/tables/DomainsTable.tsx
  • src/state/contexts/ArNSState.tsx
  • src/state/actions/dispatchArNSUpdate.ts
  • src/hooks/useMarketplaceOrder.tsx
  • src/components/pages/Settings/settingsConfig.ts
  • src/state/contexts/GlobalState.tsx
📚 Learning: 2025-12-02T17:38:52.889Z
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 899
File: src/services/turbo/BaseTokenPurchaseService.ts:0-0
Timestamp: 2025-12-02T17:38:52.889Z
Learning: In src/services/turbo/BaseTokenPurchaseService.ts, the function executeBaseTokenPurchase intentionally only handles Base token top-up to Turbo credits, not the actual ArNS purchase. The ArNS purchase is handled separately by dispatchArIOInteraction in Checkout.tsx because it manages multiple complex purchase workflows (lease, permabuy, extend, increase undernames, etc.) with their own state management. This separation of concerns is by design.

Applied to files:

  • src/components/pages/Register/Checkout.tsx
  • src/state/contexts/ArNSState.tsx
  • src/state/actions/dispatchArNSUpdate.ts
  • src/state/reducers/GlobalReducer.ts
  • src/state/contexts/GlobalState.tsx
📚 Learning: 2025-10-22T16:07:22.654Z
Learnt from: atticusofsparta
Repo: ar-io/arns-react PR: 887
File: src/state/actions/dispatchANTInteraction.ts:0-0
Timestamp: 2025-10-22T16:07:22.654Z
Learning: In the ar.io/sdk, ANT.init accepts ContractSigner directly (any signer type) and internally resolves it, while ANT.spawn requires an AoSigner and should be passed createAoSigner(signer).

Applied to files:

  • src/components/pages/Settings/devtools/ANTTools.tsx
🧬 Code graph analysis (28)
src/utils/searchUtils/searchUtils.ts (1)
src/utils/constants.ts (1)
  • URL_REGEX (90-99)
src/components/pages/Settings/devtools/MarketplaceTools.tsx (3)
src/hooks/useMarketplaceInfo.ts (1)
  • useMarketplaceInfo (4-25)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (175-176)
src/utils/constants.ts (1)
  • DEFAULT_MIN_MARKETPLACE_ANT_VERSION (29-29)
src/components/pages/Manage/Manage.tsx (1)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (175-176)
src/hooks/useMarketplaceInfo.ts (2)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (175-176)
src/state/contexts/WalletState.tsx (1)
  • useWalletState (49-50)
src/components/layout/Notifications/Notifications.tsx (1)
src/utils/errors.ts (1)
  • NotificationOnlyError (4-9)
src/components/modals/ManageMarketplaceARIOModal/panels/WithdrawPanel.tsx (8)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (175-176)
src/state/contexts/WalletState.tsx (1)
  • useWalletState (49-50)
src/hooks/useArIOPrice.ts (1)
  • useArIoPrice (9-32)
src/hooks/useMarketplaceUserAssets.ts (1)
  • useMarketplaceUserAssets (44-61)
src/utils/common/common.tsx (1)
  • formatARIOWithCommas (215-219)
src/utils/searchUtils/searchUtils.ts (1)
  • sleep (128-130)
src/utils/network.ts (1)
  • queryClient (39-45)
src/components/layout/index.ts (1)
  • Loader (12-12)
src/components/data-display/tables/MarketplaceListingsTable.tsx (11)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (175-176)
src/hooks/useArIOPrice.ts (1)
  • useArIoPrice (9-32)
src/hooks/useArNSRecords.tsx (1)
  • useArNSRecords (5-40)
src/hooks/useMarketplaceOrders.ts (1)
  • useMarketplaceOrders (72-84)
src/utils/searchUtils/searchUtils.ts (1)
  • decodeDomainToASCII (48-52)
src/utils/common/common.tsx (3)
  • formatForMaxCharCount (134-148)
  • formatARIOWithCommas (215-219)
  • formatVerboseDate (21-36)
src/components/data-display/index.tsx (1)
  • Tooltip (5-5)
src/services/arweave/ArweaveTransactionID.ts (1)
  • ArweaveTransactionID (7-31)
src/types.ts (1)
  • AoAddress (562-562)
src/components/icons/index.ts (1)
  • RefreshIcon (127-127)
src/components/layout/index.ts (1)
  • Loader (12-12)
src/components/modals/ListNameForSaleModal/panels/FixedPricePanel.tsx (4)
src/hooks/useArNSDomainPriceList.tsx (1)
  • useArNSDomainPriceList (9-83)
src/utils/common/common.tsx (1)
  • formatARIOWithCommas (215-219)
src/components/data-display/index.tsx (1)
  • Tooltip (5-5)
src/components/icons/index.ts (1)
  • InfoIcon (115-115)
src/utils/routes.tsx (1)
src/components/pages/Marketplace/Marketplace.tsx (1)
  • Marketplace (6-41)
src/components/cards/NavMenuCard/NavMenuCard.tsx (3)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (175-176)
src/hooks/useMarketplaceUserAssets.ts (1)
  • buildMarketplaceUserAssetsQuery (6-42)
src/components/modals/index.ts (1)
  • ManageMarketplaceARIOModal (14-14)
src/state/contexts/ArNSState.tsx (1)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (175-176)
src/state/actions/dispatchArNSUpdate.ts (4)
src/state/reducers/ArNSReducer.ts (1)
  • ArNSAction (10-30)
src/types.ts (1)
  • AoAddress (562-562)
src/utils/network.ts (1)
  • queryClient (39-45)
src/hooks/useMarketplaceUserAssets.ts (1)
  • buildMarketplaceUserAssetsQuery (6-42)
src/hooks/useMarketplaceOrder.tsx (2)
src/utils/constants.ts (1)
  • NETWORK_DEFAULTS (144-178)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (175-176)
src/hooks/useInterruptedWorkflows.ts (3)
src/state/contexts/ArNSState.tsx (1)
  • ANTProcessData (16-21)
src/hooks/useMarketplaceUserAssets.ts (1)
  • useMarketplaceUserAssets (44-61)
src/state/contexts/WalletState.tsx (1)
  • useWalletState (49-50)
src/hooks/useMarketplaceUserAssets.ts (3)
src/utils/constants.ts (1)
  • NETWORK_DEFAULTS (144-178)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (175-176)
src/state/contexts/WalletState.tsx (1)
  • useWalletState (49-50)
src/components/pages/Marketplace/Marketplace.tsx (2)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (175-176)
src/components/modals/index.ts (1)
  • ManageMarketplaceARIOModal (14-14)
src/hooks/useMarketplaceOrders.ts (2)
src/utils/constants.ts (1)
  • NETWORK_DEFAULTS (144-178)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (175-176)
src/components/modals/ManageMarketplaceARIOModal/panels/DepositPanel.tsx (7)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (175-176)
src/state/contexts/WalletState.tsx (1)
  • useWalletState (49-50)
src/hooks/useArIOPrice.ts (1)
  • useArIoPrice (9-32)
src/hooks/useMarketplaceUserAssets.ts (1)
  • useMarketplaceUserAssets (44-61)
src/utils/searchUtils/searchUtils.ts (1)
  • sleep (128-130)
src/utils/network.ts (1)
  • queryClient (39-45)
src/components/layout/index.ts (1)
  • Loader (12-12)
src/components/cards/MarketplaceOrderInfoCard/MarketplaceOrderInfoCard.tsx (8)
src/hooks/useDomainInfo.tsx (1)
  • useDomainInfo (206-255)
src/utils/common/common.tsx (2)
  • formatARIOWithCommas (215-219)
  • formatVerboseDate (21-36)
src/components/icons/index.ts (1)
  • ArNSLogo (72-72)
src/components/data-display/AntLogoIcon.tsx (1)
  • AntLogoIcon (7-57)
src/utils/transactionUtils/transactionUtils.tsx (1)
  • isArweaveTransactionID (40-48)
src/utils/constants.ts (1)
  • DEFAULT_ANT_LOGO (117-117)
src/components/data-display/index.tsx (1)
  • Tooltip (5-5)
src/utils/searchUtils/searchUtils.ts (1)
  • decodeDomainToASCII (48-52)
src/components/indicators/InterruptedWorkflowIndicator/InterruptedWorkflowIndicator.tsx (1)
src/components/data-display/index.tsx (1)
  • Tooltip (5-5)
src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx (3)
src/hooks/useArIOPrice.ts (1)
  • useArIoPrice (9-32)
src/hooks/useANTIntent.ts (1)
  • useANTIntent (17-75)
src/services/arweave/ArweaveTransactionID.ts (1)
  • ArweaveTransactionID (7-31)
src/components/modals/ListNameForSaleModal/panels/ConfirmListingPanel.tsx (10)
src/state/contexts/WalletState.tsx (1)
  • useWalletState (49-50)
src/hooks/useDomainInfo.tsx (1)
  • useDomainInfo (206-255)
src/utils/transactionUtils/transactionUtils.tsx (1)
  • isArweaveTransactionID (40-48)
src/hooks/useMarketplaceInfo.ts (1)
  • useMarketplaceInfo (4-25)
src/utils/common/common.tsx (1)
  • formatARIOWithCommas (215-219)
src/components/data-display/index.tsx (1)
  • Tooltip (5-5)
src/components/icons/index.ts (2)
  • InfoIcon (115-115)
  • ArNSLogo (72-72)
src/utils/constants.ts (1)
  • DEFAULT_ANT_LOGO (117-117)
src/utils/searchUtils/searchUtils.ts (1)
  • decodeDomainToASCII (48-52)
src/components/inputs/Checkbox.tsx (1)
  • Checkbox (46-46)
src/App.tsx (1)
src/utils/routes.tsx (1)
  • Route (27-35)
src/components/pages/Settings/settingsConfig.ts (1)
src/utils/constants.ts (1)
  • DEFAULT_MIN_MARKETPLACE_ANT_VERSION (29-29)
src/components/pages/Settings/NetworkSettings.tsx (2)
src/utils/searchUtils/searchUtils.ts (1)
  • isValidNetworkURL (204-241)
src/utils/constants.ts (1)
  • NETWORK_DEFAULTS (144-178)
src/hooks/useANTIntent.ts (2)
src/hooks/useMarketplaceUserAssets.ts (1)
  • useMarketplaceUserAssets (44-61)
src/state/contexts/WalletState.tsx (1)
  • useWalletState (49-50)
src/components/layout/Navbar/NotificationMenu/NotificationMenu.tsx (4)
src/hooks/useInterruptedWorkflows.ts (1)
  • useInterruptedWorkflows (15-67)
src/utils/searchUtils/searchUtils.ts (1)
  • getAntsRequiringUpdate (136-162)
src/components/data-display/index.tsx (1)
  • Tooltip (5-5)
src/components/icons/index.ts (1)
  • BellIcon (87-87)
src/state/contexts/GlobalState.tsx (1)
src/utils/constants.ts (2)
  • MARKETPLACE_PROCESS_ID (103-105)
  • DEFAULT_MIN_MARKETPLACE_ANT_VERSION (29-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_for_deploy


// set the log level of ar-io-sdk
Logger.default.setLogLevel('none');
Logger.default.setLogLevel('debug');
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 | 🟠 Major

Reconsider enabling debug logging in all environments.

Setting the log level to 'debug' for the ar-io-sdk will apply to all environments, including production. This could negatively impact performance, expose sensitive information in browser consoles, and increase noise in production logs.

Consider making the log level environment-dependent:

-Logger.default.setLogLevel('debug');
+Logger.default.setLogLevel(
+  import.meta.env.MODE === 'production' ? 'none' : 'debug'
+);
📝 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
Logger.default.setLogLevel('debug');
Logger.default.setLogLevel(
import.meta.env.MODE === 'production' ? 'none' : 'debug'
);
🤖 Prompt for AI Agents
In src/App.tsx around line 26, the code unconditionally sets
Logger.default.setLogLevel('debug'), which forces debug logging in all
environments; change this to be environment-dependent by reading NODE_ENV (or
the app's env var like REACT_APP_ENV), and only set 'debug' when NODE_ENV ===
'development' (or the explicit dev env), otherwise set a less verbose default
such as 'info' or 'warn'; ensure a safe fallback if the env var is missing so
production never defaults to 'debug'.

import { Tooltip } from '@src/components/data-display';
import { AntLogoIcon } from '@src/components/data-display/AntLogoIcon';
import { ArNSLogo } from '@src/components/icons';
import { Checkbox } from '@src/components/inputs/Checkbox';
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove unused import.

Checkbox is imported but not used anywhere in this component.

-import { Checkbox } from '@src/components/inputs/Checkbox';
📝 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
import { Checkbox } from '@src/components/inputs/Checkbox';
🤖 Prompt for AI Agents
In src/components/cards/MarketplaceOrderInfoCard/MarketplaceOrderInfoCard.tsx
around line 5, there is an unused import "Checkbox" from
'@src/components/inputs/Checkbox'; remove that import line to eliminate the
unused dependency and resolve the linter warning, and run the project/linter to
ensure no further unused-imports remain.

Comment on lines 156 to 161
<Link
to={`/marketplace/listing/${name}`}
className="text-link hover:text-primary-light transition-colors font-medium"
>
{formatForMaxCharCount(decodedName, 30)}
</Link>
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 | 🟠 Major

Inconsistent URL path for listing links.

Line 157 uses /marketplace/listing/${name} while line 125 uses /marketplace/names/${name}. These should be consistent to avoid routing issues.

Verify which path is correct and update for consistency:

                 <Link
-                  to={`/marketplace/listing/${name}`}
+                  to={`/marketplace/names/${name}`}
                   className="text-link hover:text-primary-light transition-colors font-medium"
                 >
📝 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
<Link
to={`/marketplace/listing/${name}`}
className="text-link hover:text-primary-light transition-colors font-medium"
>
{formatForMaxCharCount(decodedName, 30)}
</Link>
<Link
to={`/marketplace/names/${name}`}
className="text-link hover:text-primary-light transition-colors font-medium"
>
{formatForMaxCharCount(decodedName, 30)}
</Link>
🤖 Prompt for AI Agents
In src/components/data-display/tables/MarketplaceListingsTable.tsx around lines
156 to 161, the link path uses `/marketplace/listing/${name}` which is
inconsistent with `/marketplace/names/${name}` used elsewhere; confirm the
correct route (likely `/marketplace/names/${name}`) and update this Link to use
the same path, and then run/adjust any affected tests or usages to ensure
routing remains consistent.

value?: Date;
onChange: (date: Date | undefined) => void;
placeholder?: string;
minDate?: Date;
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 | 🟠 Major

Remove unused minDate prop or implement its logic.

The minDate prop is defined in the interface but never used in the component logic. This is misleading to consumers of the component who might expect it to constrain date selection.

Apply this diff to remove the unused prop:

 interface DateTimePickerProps {
   value?: Date;
   onChange: (date: Date | undefined) => void;
   placeholder?: string;
-  minDate?: Date;
   className?: string;
 }

Alternatively, if minDate is intended to be used, implement it in the isDateDisabled function at Line 66.

📝 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
minDate?: Date;
interface DateTimePickerProps {
value?: Date;
onChange: (date: Date | undefined) => void;
placeholder?: string;
className?: string;
}
🤖 Prompt for AI Agents
In src/components/inputs/DateTimePicker/DateTimePicker.tsx around line 10 (prop
definition) and line 66 (isDateDisabled), the minDate prop is declared but
unused; either remove it from the component props and related types/usages, or
implement it in isDateDisabled. To implement: update isDateDisabled to
early-return true when a candidate date is before the provided minDate (handle
undefined minDate), comparing dates normalized to the same time basis (e.g.,
strip time or use getTime()) so selection is correctly constrained; to remove:
delete minDate from the props interface and any stories/tests/usages that pass
it. Ensure TypeScript types and exported prop docs are updated accordingly.

Comment on lines 160 to 164
id={
result.antTransferResult?.id
? new ArweaveTransactionID(result.antTransferResult?.id)
: '0xanErrorOccured'
}
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 | 🟠 Major

Error state will throw: invalid ArweaveTransactionID.

If result.antTransferResult?.id is undefined, creating new ArweaveTransactionID('0xanErrorOccured') will throw an error because the constructor validates that the ID must be a 43-character alphanumeric string (per ArweaveTransactionID.ts lines 6-12). This could crash the success panel.

-                  id={
-                    result.antTransferResult?.id
-                      ? new ArweaveTransactionID(result.antTransferResult?.id)
-                      : '0xanErrorOccured'
-                  }
+                  id={
+                    result.antTransferResult?.id
+                      ? new ArweaveTransactionID(result.antTransferResult.id)
+                      : undefined
+                  }

Then handle the undefined case in the ArweaveID component or conditionally render this section only when result.antTransferResult?.id exists.

📝 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
id={
result.antTransferResult?.id
? new ArweaveTransactionID(result.antTransferResult?.id)
: '0xanErrorOccured'
}
id={
result.antTransferResult?.id
? new ArweaveTransactionID(result.antTransferResult.id)
: undefined
}
🤖 Prompt for AI Agents
In src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx around
lines 160-164, the code constructs new ArweaveTransactionID('0xanErrorOccured')
when result.antTransferResult?.id is missing, which triggers the
ArweaveTransactionID constructor validation and will throw; change the logic to
only instantiate ArweaveTransactionID when result.antTransferResult?.id is a
valid non-empty string (e.g., conditionally render this component/section only
if result.antTransferResult?.id exists) or modify the prop passed to the
ArweaveID component to be undefined/null when id is absent and let ArweaveID
handle the empty state gracefully (do not pass an invalid placeholder string).

Comment on lines +102 to +104
const marketPriceDisplay = domainPrices?.buy
? `Market Value: ${formatARIOWithCommas(Math.ceil(domainPrices.buy / 1000000))} ${arioTicker}`
: '';
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Inconsistent mARIO to ARIO conversion.

Lines 51-53 correctly use new mARIOToken(domainPrices.buy).toARIO().valueOf(), but lines 103, 113, and 117 use manual division by 1000000. Use the SDK's conversion method consistently to avoid errors if the decimal precision changes.

-  const marketPriceDisplay = domainPrices?.buy
-    ? `Market Value: ${formatARIOWithCommas(Math.ceil(domainPrices.buy / 1000000))} ${arioTicker}`
-    : '';
+  const marketValueARIO = domainPrices?.buy
+    ? Math.ceil(new mARIOToken(domainPrices.buy).toARIO().valueOf())
+    : 0;
+  const marketPriceDisplay = marketValueARIO > 0
+    ? `Market Value: ${formatARIOWithCommas(marketValueARIO)} ${arioTicker}`
+    : '';

Then use marketValueARIO in the JSX instead of recalculating.

Also applies to: 113-117

🤖 Prompt for AI Agents
In src/components/modals/ListNameForSaleModal/panels/FixedPricePanel.tsx around
lines 102-117, the code manually divides domainPrices.buy by 1,000,000 to
convert mARIO->ARIO; replace those manual divisions by using the SDK conversion
used earlier (new mARIOToken(domainPrices.buy).toARIO().valueOf()), assign the
result to a marketValueARIO variable (e.g., const marketValueARIO =
domainPrices?.buy ? new mARIOToken(domainPrices.buy).toARIO().valueOf() : 0) and
then use marketValueARIO when building the market price display and in the JSX
instead of recalculating with /1000000; ensure proper null/undefined handling to
match existing behavior.

Comment on lines +133 to +144
{PRICE_BUTTON_VALUES.map((value, index) => (
<button
key={index}
className={`rounded p-2.5 transition-colors ${
buttonSelected === index
? 'bg-white text-black'
: 'bg-dark-grey text-white hover:bg-gray-600'
}`}
onClick={() => {
setCustomValue(value.toString());
setListingPrice(value);
setButtonSelected(index);
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 | 🟠 Major

Bug: button selection comparison uses index instead of value.

buttonSelected === index compares the selected value with the loop index, but setButtonSelected(index) stores the index. However, lines 64-67 try to find a matching button by value: PRICE_BUTTON_VALUES.find((value) => value === listingPrice). This creates an inconsistency where returning from the confirm screen sets buttonSelected to the value, but the rendering compares against the index.

Either consistently use values:

 {PRICE_BUTTON_VALUES.map((value, index) => (
   <button
     key={index}
     className={`rounded p-2.5 transition-colors ${
-      buttonSelected === index
+      buttonSelected === value
         ? 'bg-white text-black'
         : 'bg-dark-grey text-white hover:bg-gray-600'
     }`}
     onClick={() => {
       setCustomValue(value.toString());
       setListingPrice(value);
-      setButtonSelected(index);
+      setButtonSelected(value);
       setCustomValueError('');
     }}
📝 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
{PRICE_BUTTON_VALUES.map((value, index) => (
<button
key={index}
className={`rounded p-2.5 transition-colors ${
buttonSelected === index
? 'bg-white text-black'
: 'bg-dark-grey text-white hover:bg-gray-600'
}`}
onClick={() => {
setCustomValue(value.toString());
setListingPrice(value);
setButtonSelected(index);
{PRICE_BUTTON_VALUES.map((value, index) => (
<button
key={index}
className={`rounded p-2.5 transition-colors ${
buttonSelected === value
? 'bg-white text-black'
: 'bg-dark-grey text-white hover:bg-gray-600'
}`}
onClick={() => {
setCustomValue(value.toString());
setListingPrice(value);
setButtonSelected(value);
🤖 Prompt for AI Agents
In src/components/modals/ListNameForSaleModal/panels/FixedPricePanel.tsx around
lines 133 to 144, the rendered button selection compares buttonSelected ===
index but elsewhere buttonSelected is being set from a value (listingPrice) when
returning from the confirm screen, causing mismatch; make the stored and
compared identifier consistent — either store and compare the actual price value
or consistently store and compare the index. Fix by choosing one approach and
updating all usages: if you prefer values, call setButtonSelected(value) in the
onClick and change the render comparison to buttonSelected === value (and ensure
initial state and any find/lookup use the value); if you prefer indices, ensure
when restoring from listingPrice you compute the matching index (e.g., findIndex
on PRICE_BUTTON_VALUES) and call setButtonSelected(index) so rendering remains
index-based.

Comment on lines +136 to +143
const writeMarketplaceContract = new ArNSMarketplaceWrite({
process: new AOProcess({
processId: marketplaceProcessId,
ao: aoClient,
}),
signer: createAoSigner(wallet.contractSigner),
ario: arioContract as AoARIOWrite,
});
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find GlobalState file and search for arioContract
find . -name "GlobalState.tsx" -o -name "GlobalState.ts" 2>/dev/null | head -5

Repository: ar-io/arns-react

Length of output: 96


🏁 Script executed:

#!/bin/bash
# Read GlobalState.tsx to see arioContract initialization
cat -n ./src/state/contexts/GlobalState.tsx | head -100

Repository: ar-io/arns-react

Length of output: 4118


🏁 Script executed:

#!/bin/bash
# Continue reading GlobalState.tsx to find arioContract initialization
cat -n ./src/state/contexts/GlobalState.tsx | sed -n '100,250p'

Repository: ar-io/arns-react

Length of output: 4916


🏁 Script executed:

#!/bin/bash
# Find the GlobalReducer to see how arioContract state changes
find . -name "GlobalReducer*" -o -name "*Reducer.ts*" | grep -i global

Repository: ar-io/arns-react

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Read the GlobalReducer to see how arioContract is updated
cat -n ./src/state/reducers/GlobalReducer.ts

Repository: ar-io/arns-react

Length of output: 4629


🏁 Script executed:

#!/bin/bash
# Read the WithdrawPanel to see the exact context around the cast
cat -n ./src/components/modals/ManageMarketplaceARIOModal/panels/WithdrawPanel.tsx | sed -n '130,150p'

Repository: ar-io/arns-react

Length of output: 880


Remove unsafe cast or add runtime type check for arioContract.

The GlobalState types arioContract as AoARIORead | AoARIOWrite, and it is initialized with ARIO.init() without a signer, creating a read-only instance. The cast arioContract as AoARIOWrite at line 142 assumes the contract is always writeable without verification. Since arioContract can remain read-only or be updated to either read or write capability via the reducer, this cast is unsafe and will cause runtime errors if ArNSMarketplaceWrite attempts signing operations with a read-only contract instance.

Comment on lines +24 to 31
Sentry.browserTracingIntegration(),
Sentry.reactRouterV6BrowserTracingIntegration({
useEffect,
useLocation,
useNavigationType,
createRoutesFromChildren,
matchRoutes,
}),
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

Remove the redundant browserTracingIntegration() integration.

For React Router v6, use only reactRouterV6BrowserTracingIntegration(). The Sentry documentation states to "update your Sentry.browserTracingIntegration to Sentry.reactRouterV6BrowserTracingIntegration", indicating a replacement. Using both integrations together will create duplicate tracing spans and unnecessary overhead. Remove Sentry.browserTracingIntegration() from line 24.

🤖 Prompt for AI Agents
In src/utils/sentry.ts around lines 24 to 31, the
Sentry.browserTracingIntegration() call is redundant when using
Sentry.reactRouterV6BrowserTracingIntegration; remove the
Sentry.browserTracingIntegration() entry from the integrations list so only
Sentry.reactRouterV6BrowserTracingIntegration({...}) remains, ensuring you leave
the reactRouterV6 integration and its passed hooks intact and update any imports
if necessary.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/pages/Settings/NetworkSettings.tsx (1)

492-492: Add validation guard to SU Address onPressEnter handler.

The SU Address onPressEnter handler lacks validation, which is inconsistent with the pattern established for other inputs in this file. Transaction ID inputs (AR.IO Contract at lines 330-337 and ANT Registry at lines 369-376) validate with isArweaveTransactionID before dispatching changes.

Apply this diff to add validation:

-          onPressEnter={(value) => updateAoNetwork({ SCHEDULER: value })}
+          onPressEnter={(value) => {
+            if (isArweaveTransactionID(value)) {
+              updateAoNetwork({ SCHEDULER: value });
+            }
+          }}
🧹 Nitpick comments (3)
package.json (2)

61-61: permaweb-deploy should be a devDependency.

This is a deployment CLI tool, not a runtime dependency. Moving it to devDependencies reduces the production bundle footprint.

   "dependencies": {
     ...
-    "permaweb-deploy": "^2.5.1",
     ...
   },
   "devDependencies": {
     ...
+    "permaweb-deploy": "^2.5.1",
     ...
   }

52-53: Redundant date libraries: date-fns and dayjs.

Both date-fns and dayjs are date manipulation libraries. Consider standardizing on one to reduce bundle size, unless there's a specific reason for both (e.g., existing codebase uses one, new features use another for a specific capability).

src/services/wallets/EthWalletConnector.ts (1)

79-81: Remove @ts-ignore and document the type compatibility workaround or correct the signature mismatch.

The aoSigner function has a @ts-ignore directive and defaults empty data to a space character, both suggesting a known incompatibility with the AoSigner interface from @ar.io/sdk. This approach masks the type mismatch rather than addressing it.

Consider one of:

  1. If data can legitimately be absent: Update the type signature to explicitly mark data as optional and document when this occurs and why defaulting to a space is acceptable for signing operations.
  2. If data should always be present: Remove the default guard and fix the underlying type compatibility issue by ensuring the implementation matches the interface contract.

The current pattern with @ts-ignore and silent defaulting makes the code difficult to maintain and could hide integration issues with the SDK in the future.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1788b5e and d81e577.

📒 Files selected for processing (5)
  • package.json (3 hunks)
  • src/components/pages/Settings/NetworkSettings.tsx (7 hunks)
  • src/services/wallets/EthWalletConnector.ts (1 hunks)
  • src/services/wallets/WanderWalletConnector.ts (3 hunks)
  • src/state/contexts/GlobalState.tsx (8 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/services/wallets/WanderWalletConnector.ts
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 873
File: src/components/modals/ant-management/UpgradeDomainsModal/UpgradeDomainsModal.tsx:0-0
Timestamp: 2025-09-10T16:35:55.727Z
Learning: In arns-react, when calling buildDomainInfoQuery (src/hooks/useDomainInfo.tsx), you must pass arioProcessId along with arioContract, domain, aoNetwork, hyperbeamUrl, and antRegistryProcessId; omitting arioProcessId can lead to “No ANT id or record found” and break upgrades.
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 873
File: src/components/modals/ant-management/UpgradeDomainsModal/UpgradeDomainsModal.tsx:0-0
Timestamp: 2025-09-10T16:35:55.727Z
Learning: In arns-react, when calling buildDomainInfoQuery (src/hooks/useDomainInfo.tsx), you must pass arioProcessId along with arioContract, domain, aoNetwork, hyperbeamUrl, and antRegistryProcessId; omitting arioProcessId can lead to "No ANT id or record found" and break upgrades.
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 899
File: src/services/turbo/BaseTokenPurchaseService.ts:0-0
Timestamp: 2025-12-02T17:38:52.889Z
Learning: In src/services/turbo/BaseTokenPurchaseService.ts, the function executeBaseTokenPurchase intentionally only handles Base token top-up to Turbo credits, not the actual ArNS purchase. The ArNS purchase is handled separately by dispatchArIOInteraction in Checkout.tsx because it manages multiple complex purchase workflows (lease, permabuy, extend, increase undernames, etc.) with their own state management. This separation of concerns is by design.
📚 Learning: 2025-12-11T05:20:18.825Z
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 902
File: src/components/modals/turbo/panels/crypto/CryptoConfirmation.tsx:536-540
Timestamp: 2025-12-11T05:20:18.825Z
Learning: Guideline: For tokens on Base network that use 6 decimals (e.g., base-ario ERC-20), convert amount to smallest units using a 1e6 multiplier. Use Math.round(amount * 1e6) when computing the on-chain value. Do not assume 18 decimals. Apply this conversion consistently in code paths that perform payments in CryptoConfirmation.tsx or similar components, and document that the base-ario/USDC-like tokens use 6 decimals to avoid mispricing or transfer errors.

Applied to files:

  • src/components/pages/Settings/NetworkSettings.tsx
📚 Learning: 2025-12-16T16:47:36.348Z
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 904
File: .github/workflows/pr-preview.yaml:52-58
Timestamp: 2025-12-16T16:47:36.348Z
Learning: In the ar-io/arns-react repository, ArNS undername cleanup for PR previews is handled by the permaweb-deploy GitHub action, not by a local script in this repository. The cleanup step should not be included in the PR preview workflow.

Applied to files:

  • package.json
📚 Learning: 2025-12-02T17:38:52.889Z
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 899
File: src/services/turbo/BaseTokenPurchaseService.ts:0-0
Timestamp: 2025-12-02T17:38:52.889Z
Learning: In src/services/turbo/BaseTokenPurchaseService.ts, the function executeBaseTokenPurchase intentionally only handles Base token top-up to Turbo credits, not the actual ArNS purchase. The ArNS purchase is handled separately by dispatchArIOInteraction in Checkout.tsx because it manages multiple complex purchase workflows (lease, permabuy, extend, increase undernames, etc.) with their own state management. This separation of concerns is by design.

Applied to files:

  • src/state/contexts/GlobalState.tsx
📚 Learning: 2025-09-10T16:35:55.727Z
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 873
File: src/components/modals/ant-management/UpgradeDomainsModal/UpgradeDomainsModal.tsx:0-0
Timestamp: 2025-09-10T16:35:55.727Z
Learning: In arns-react, when calling buildDomainInfoQuery (src/hooks/useDomainInfo.tsx), you must pass arioProcessId along with arioContract, domain, aoNetwork, hyperbeamUrl, and antRegistryProcessId; omitting arioProcessId can lead to "No ANT id or record found" and break upgrades.

Applied to files:

  • src/state/contexts/GlobalState.tsx
📚 Learning: 2025-09-10T16:35:55.727Z
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 873
File: src/components/modals/ant-management/UpgradeDomainsModal/UpgradeDomainsModal.tsx:0-0
Timestamp: 2025-09-10T16:35:55.727Z
Learning: In arns-react, when calling buildDomainInfoQuery (src/hooks/useDomainInfo.tsx), you must pass arioProcessId along with arioContract, domain, aoNetwork, hyperbeamUrl, and antRegistryProcessId; omitting arioProcessId can lead to “No ANT id or record found” and break upgrades.

Applied to files:

  • src/state/contexts/GlobalState.tsx
🧬 Code graph analysis (1)
src/state/contexts/GlobalState.tsx (1)
src/utils/constants.ts (2)
  • MARKETPLACE_PROCESS_ID (103-105)
  • DEFAULT_MIN_MARKETPLACE_ANT_VERSION (29-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: lint_and_build
🔇 Additional comments (14)
package.json (1)

23-29: Confirm intentional use of alpha dependencies in production.

Using alpha versions (@ar.io/sdk 3.23.0-alpha.1, @ardrive/turbo-sdk ^1.35.0-alpha.2) in production carries stability risks. Ensure the team is prepared to handle potential breaking changes.

src/components/pages/Settings/NetworkSettings.tsx (5)

21-26: LGTM: Clean import of new validator.

The addition of isValidNetworkURL supports the validation improvements throughout this file.


452-452: Consistent validation pattern for MU_URL.

The changes mirror the CU_URL validation improvements and maintain consistency across network configuration inputs.

Also applies to: 460-464


526-526: Consistent validation for Hyperbeam URL with optional field handling.

The validation changes are consistent with other network URLs. Note that Hyperbeam URL is optional (can be undefined/disabled), and the onPressEnter validation guard means users cannot clear the field by entering an empty string and pressing Enter. However, the Set button (lines 528-533) properly handles clearing via the trim() || undefined logic, so the UX remains functional.

Also applies to: 545-552


565-565: Consistent validation pattern for Turbo Payment URL.

The changes align with the validation improvements for other network URLs and maintain consistency across the file.

Also applies to: 584-590


427-427: Switch to isValidNetworkURL for network endpoint validation is sound.

The implementation properly validates AO network endpoint formats (CU_URL, MU_URL) by checking for HTTPS/HTTP protocols and validating domain structure, including subdomains like cu.ao-testnet.xyz. The addition of validation guards in onPressEnter provides good defensive programming.

src/state/contexts/GlobalState.tsx (8)

34-35: LGTM!

The marketplace constants are properly imported and align with their definitions in src/utils/constants.ts.


41-82: LGTM!

The loadSettingsFromStorage function is correctly extended to load marketplace settings with proper fallbacks to constants. The implementation follows the established pattern for other settings.


95-99: LGTM!

Initial marketplace variables are properly initialized with the correct fallback chain from saved settings to constants.


125-142: LGTM!

The GlobalState type is correctly extended with marketplace fields. The union type ArNSMarketplaceRead | ArNSMarketplaceWrite appropriately mirrors the pattern used for arioContract, allowing the contract to be upgraded when a wallet connects.


144-172: LGTM!

The initialState is correctly extended with marketplace fields and follows consistent patterns:

  • Marketplace fields are properly initialized with the constants and defaults
  • The network configuration changes to aoClient and antAoClient mirror the defaultArIO pattern
  • marketplaceContract is properly initialized with defaultMarketplaceContract

8-9: Upgrade to latest stable SDK version when marketplace support is available.

The code uses @ar.io/sdk version 3.23.0-alpha.1 (alpha) for marketplace types, but the latest stable version is 3.18.3. Plan to upgrade to a stable release once marketplace support is added to it, as alpha versions carry stability risks and may introduce breaking changes.


115-123: Verify marketplace contract configuration, especially MU_URL requirement.

The defaultMarketplaceContract is configured without MU_URL, while defaultArIO includes both CU_URL and MU_URL from the same initialAoNetwork.ARIO source. Although ArNSMarketplaceRead is read-only, @ar.io/sdk documentation consistently shows both parameters in all AOProcess configuration examples. Confirm whether:

  1. MU_URL is truly optional for read-only ArNSMarketplaceRead contracts, or if it should be included like defaultArIO
  2. Whether the marketplace process should use its own dedicated endpoint configuration instead of sharing initialAoNetwork.ARIO.CU_URL

102-113: No issue found. The code correctly initializes defaultArIO with explicit CU_URL, MU_URL, and MODE: 'legacy' properties. These are the only properties required by the connect() function for ARIO initialization, which aligns with the official ARIO SDK documentation. Other network properties like SCHEDULER and HYPERBEAM_URL are used separately in transaction creation and ARIO initialization respectively, not in the connect() call. The pattern is consistent across all connect() usages in the file (aoClient, antAoClient, defaultMarketplaceContract).

Comment on lines +36 to 37
"@sentry/browser": "^10.29.0",
"@sentry/react": "^7.45.0",
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 | 🟠 Major

Sentry version mismatch between @sentry/browser and @sentry/react.

@sentry/browser at ^10.29.0 and @sentry/react at ^7.45.0 are incompatible major versions. Sentry packages should use matching major versions to avoid runtime conflicts and undefined behavior.

-    "@sentry/browser": "^10.29.0",
-    "@sentry/react": "^7.45.0",
+    "@sentry/browser": "^7.45.0",
+    "@sentry/react": "^7.45.0",

Or upgrade both to v10 if you need the latest features:

-    "@sentry/browser": "^10.29.0",
-    "@sentry/react": "^7.45.0",
+    "@sentry/browser": "^10.29.0",
+    "@sentry/react": "^10.29.0",
📝 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
"@sentry/browser": "^10.29.0",
"@sentry/react": "^7.45.0",
"@sentry/browser": "^7.45.0",
"@sentry/react": "^7.45.0",
🤖 Prompt for AI Agents
In package.json around lines 36-37, the project lists @sentry/browser at
^10.29.0 and @sentry/react at ^7.45.0 which are mismatched major versions;
update the Sentry packages to use the same major version—either bump
@sentry/react to a v10 release matching @sentry/browser (e.g., ^10.29.0) or
downgrade @sentry/browser to the v7 series to match @sentry/react; after
changing the version strings run your package manager install (npm/yarn/pnpm)
and run the test/lint scripts to ensure nothing else breaks.

@github-actions
Copy link

github-actions bot commented Dec 16, 2025

Permaweb Preview Deployed

Your preview is available at: https://pr-903_arns.arweave.net

Property Value
Transaction ID bp3wfEVInRQSHs-RH9_2jYoeWSb876U1zmQKIG4j7PY
ArNS Name arns
Undername pr-903

This preview will be available permanently on Arweave.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
package.json (1)

6-20: Remove test infrastructure or document testing strategy.

The complete test infrastructure has been removed: no test configuration files (jest, playwright, vitest), test files, or test dependencies remain in the repository. This PR introduces substantial marketplace functionality without any automated tests, significantly increasing regression risk.

Either:

  1. Restore the test suite with coverage for the new marketplace features, or
  2. Document the testing strategy and quality assurance approach used to validate this code
♻️ Duplicate comments (1)
package.json (1)

36-37: Sentry version mismatch remains unresolved.

This issue was previously flagged: @sentry/browser (^10.29.0) and @sentry/react (^7.45.0) use incompatible major versions. Please align them to the same major version to prevent runtime conflicts.

🧹 Nitpick comments (2)
src/components/data-display/tables/MarketplaceListingsTable.tsx (2)

83-95: Consider server-side pagination for larger datasets.

The hard-coded limit: 100 may be insufficient as the marketplace grows. While the API supports pagination (hasMore, nextCursor), the component doesn't expose pagination controls to users or implement fetching beyond the first page.

Consider either:

  1. Increasing the limit significantly (e.g., 1000) if the total listing count is expected to remain modest, or
  2. Implementing proper pagination controls that allow users to navigate through all listings, fetching additional pages as needed.

410-434: Unconventional but functional loading state management.

The component passes isLoading={false} to TableView while managing loading state through noDataFoundText and an empty data array. This works correctly but diverges from the typical pattern where TableView's isLoading prop would control the loading indicator.

If you prefer conventional table loading patterns, consider:

Alternative approach using TableView's isLoading prop
      <TableView
        columns={columns as ColumnDef<MarketplaceListing>[]}
-       data={loadingTableData ? [] : filteredData}
-       isLoading={false}
+       data={filteredData}
+       isLoading={loadingTableData}
        defaultSortingState={{ id: 'listedAt', desc: true }}
        noDataFoundText={
-         loadingTableData ? (
-           <div className="flex flex-col items-center justify-center p-8 text-center">
-             <ARIOLoadingSpinner />
-           </div>
-         ) : (
            <div className="flex flex-col items-center justify-center p-8 text-center">
              <div className="text-grey mb-4">
                No marketplace listings found
              </div>
              <button
                onClick={() => refetch()}
                className="flex items-center gap-2 px-4 py-2 border-white text-white rounded transition-colors"
              >
                <RefreshIcon className="w-4 h-4 fill-white" />
                Retry
              </button>
            </div>
-         )
        }
      />

However, your current implementation is perfectly functional and may provide better UX if TableView's default loading indicator doesn't fit your design requirements.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d81e577 and ab92794.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (5)
  • package.json (3 hunks)
  • src/components/data-display/tables/MarketplaceListingsTable.tsx (1 hunks)
  • src/components/data-display/tables/TableView.tsx (1 hunks)
  • src/components/indicators/ARIOLoadingSpinner.tsx (1 hunks)
  • src/components/pages/Marketplace/Marketplace.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/pages/Marketplace/Marketplace.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 899
File: src/services/turbo/BaseTokenPurchaseService.ts:0-0
Timestamp: 2025-12-02T17:38:52.889Z
Learning: In src/services/turbo/BaseTokenPurchaseService.ts, the function executeBaseTokenPurchase intentionally only handles Base token top-up to Turbo credits, not the actual ArNS purchase. The ArNS purchase is handled separately by dispatchArIOInteraction in Checkout.tsx because it manages multiple complex purchase workflows (lease, permabuy, extend, increase undernames, etc.) with their own state management. This separation of concerns is by design.
📚 Learning: 2025-12-11T05:20:18.825Z
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 902
File: src/components/modals/turbo/panels/crypto/CryptoConfirmation.tsx:536-540
Timestamp: 2025-12-11T05:20:18.825Z
Learning: Guideline: For tokens on Base network that use 6 decimals (e.g., base-ario ERC-20), convert amount to smallest units using a 1e6 multiplier. Use Math.round(amount * 1e6) when computing the on-chain value. Do not assume 18 decimals. Apply this conversion consistently in code paths that perform payments in CryptoConfirmation.tsx or similar components, and document that the base-ario/USDC-like tokens use 6 decimals to avoid mispricing or transfer errors.

Applied to files:

  • src/components/indicators/ARIOLoadingSpinner.tsx
  • src/components/data-display/tables/TableView.tsx
  • src/components/data-display/tables/MarketplaceListingsTable.tsx
📚 Learning: 2025-08-08T16:53:05.995Z
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 841
File: src/components/data-display/tables/UndernamesTable.tsx:59-59
Timestamp: 2025-08-08T16:53:05.995Z
Learning: In src/components/data-display/tables/UndernamesTable.tsx, the isLoading prop should be optional and default to false via destructuring.

Applied to files:

  • src/components/data-display/tables/TableView.tsx
🧬 Code graph analysis (1)
src/components/data-display/tables/MarketplaceListingsTable.tsx (9)
src/hooks/useArIOPrice.ts (1)
  • useArIoPrice (9-32)
src/hooks/useArNSRecords.tsx (1)
  • useArNSRecords (5-40)
src/hooks/useMarketplaceOrders.ts (1)
  • useMarketplaceOrders (72-84)
src/components/data-display/index.tsx (1)
  • Tooltip (5-5)
src/utils/searchUtils/searchUtils.ts (2)
  • decodeDomainToASCII (48-52)
  • lowerCaseDomain (132-134)
src/utils/common/common.tsx (3)
  • formatForMaxCharCount (134-148)
  • formatARIOWithCommas (215-219)
  • formatVerboseDate (21-36)
src/services/arweave/ArweaveTransactionID.ts (1)
  • ArweaveTransactionID (7-31)
src/types.ts (1)
  • AoAddress (562-562)
src/components/icons/index.ts (1)
  • RefreshIcon (127-127)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: lint_and_build
🔇 Additional comments (4)
package.json (1)

23-23: Use stable SDK versions instead of pre-release builds.

@ar.io/sdk has a stable version 3.18.3 available, and @ardrive/turbo-sdk has a stable version 1.30.0 available. The current alpha versions in package.json introduce unnecessary risk. Migrate to the latest stable releases unless the alpha features are specifically required for marketplace functionality.

src/components/data-display/tables/TableView.tsx (1)

220-220: LGTM - UI styling improvement.

The change from border-b to border-t with added rounded class improves the visual presentation of the no-data indicator, aligning it with the table structure above.

src/components/indicators/ARIOLoadingSpinner.tsx (1)

1-17: LGTM - Clean and simple loading indicator.

The component is well-structured with sensible defaults and straightforward Lottie integration. The parameterized size prop and optional className provide good flexibility for different use cases across the marketplace UI.

src/components/data-display/tables/MarketplaceListingsTable.tsx (1)

1-440: Comprehensive marketplace table implementation with good UX.

The component demonstrates solid engineering with:

  • Proper data fetching and transformation using multiple hooks
  • Resolved past issues (null name filtering, consistent URL routing)
  • Comprehensive column definitions with appropriate sorting and formatting
  • Good error handling and user feedback mechanisms
  • Effective use of React patterns (memoization, effects, etc.)

The pagination limitation and loading state pattern have been noted separately for consideration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (3)
package.json (1)

36-37: Sentry version mismatch between @sentry/browser and @sentry/react.

This issue was previously flagged: @sentry/browser at ^10.29.0 and @sentry/react at ^7.45.0 are incompatible major versions. Please align both packages to the same major version to avoid runtime conflicts.

src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx (1)

364-395: Infinite loop risk if balance check succeeds but loop continues.

The while loop on line 367 checks newARIOBalance < feeDetails.listingFee, but inside the loop, newARIOBalance is only updated within the try block. If an exception occurs repeatedly without updating newARIOBalance, the loop will continue until maxTries is exhausted, which is expected. However, if the balance check succeeds (line 380-382 doesn't throw), the loop should break but currently continues.

Also, the loop will exit on success when the while condition becomes false, but the success path on lines 380-385 incorrectly throws when newARIOBalance <= feeDetails.listingFee.

This is related to the logic error above. The combined fix should ensure the loop exits correctly on success.

src/components/modals/ContinueWorkflowModal/ContinueWorkflowModal.tsx (1)

94-118: Inconsistent price unit handling in orderConfig.

At line 96, price is assigned as Number(orderParams.price) without division, but at lines 108-112, startingPrice and reservePrice divide by 1000000 to convert from mARIO. This inconsistency could lead to displaying incorrect values.

🔎 Proposed fix
     const listingType = orderParams.orderType || 'fixed';
-    const price = orderParams.price ? Number(orderParams.price) : 0;
+    const price = orderParams.price ? Number(orderParams.price) / 1000000 : 0;
     const expirationDate = orderParams.expirationTime

Alternatively, use mARIOToken from the SDK for consistent conversion:

const price = orderParams.price 
  ? new mARIOToken(Number(orderParams.price)).toARIO().valueOf()
  : 0;
🧹 Nitpick comments (8)
package.json (1)

61-61: Move permaweb-deploy to devDependencies.

permaweb-deploy is a deployment CLI tool used only in build/deploy scripts. It doesn't need to be bundled with production dependencies.

 "dependencies": {
   ...
-    "permaweb-deploy": "^2.5.1",
   ...
 },
 "devDependencies": {
+    "permaweb-deploy": "^2.5.1",
   ...
 }
src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx (2)

107-109: Consider a longer default expiration period.

The default expiration of 1 hour from now is quite short. Users may not complete the listing in time, or potential buyers may have limited time to discover and purchase. Consider a default of 24 hours or longer.

   const [expirationDate, setExpirationDate] = useState<Date | undefined>(
-    new Date(Date.now() + 60 * 60 * 1000), // Default to 1 hour from now (current day)
+    new Date(Date.now() + 24 * 60 * 60 * 1000), // Default to 24 hours from now
   );

489-503: Query reset predicates may match unintended queries.

The string-based query key matching with startsWith('marketplace') and includes(domainName) could inadvertently reset unrelated queries. Consider using more specific query keys or exact matching.

🔎 Suggested improvement
                           queryClient.resetQueries({
                             predicate: (query) =>
                               query.queryKey.some(
                                 (key: unknown) =>
-                                  typeof key === 'string' && key.startsWith('marketplace'),
+                                  key === 'marketplace-orders' || 
+                                  key === 'marketplace-intents' ||
+                                  key === 'marketplace-user-assets',
                               ),
                           });
src/components/data-display/VerticalTimelineStepper.tsx (1)

15-39: Consider caching the entries array to avoid repeated computation.

Object.entries(steps) is called twice per render (once in the map and once for length check). Caching it improves efficiency.

🔎 Suggested improvement
 export default function VerticalTimelineStepper({
   steps,
 }: {
   steps: Record<
     string,
     {
       title: ReactNode;
       description: ReactNode;
       icon: ReactNode;
     }
   >;
 }) {
+  const stepEntries = Object.entries(steps);
   return (
     <ol className="relative gap-2">
-      {Object.entries(steps).map(([, { title, description, icon }], i) => (
+      {stepEntries.map(([key, { title, description, icon }], i) => (
-        <div key={i}>
+        <div key={key}>
           <li className="ms-7">
             <span className="absolute flex items-center justify-center w-8 h-8 rounded-full -start-4 ring-1 ring-dark-grey">
               {icon}
             </span>
             <div className="flex flex-col max-h-8 overflow-visible">
               {' '}
               <h3 className="text-md leading-tight">{title}</h3>
               <p className="text-xs text-grey">{description}</p>
             </div>
           </li>
           <div
             className={
-              i === Object.entries(steps).length - 1
+              i === stepEntries.length - 1
                 ? ''
                 : 'border-l border-dark-grey h-8'
             }
-            key={`${i}-divider`}
           />
         </div>
       ))}
     </ol>
   );
 }
src/components/modals/ContinueWorkflowModal/ContinueWorkflowModal.tsx (1)

188-191: Avoid any type casts for better type safety.

Using as any on line 188 and any for result on line 191 loses TypeScript's type checking benefits. Consider using proper types or creating a union type if the exact type is complex.

-        ario: arioContract as any, // Cast to avoid type issues
+        ario: arioContract as AoARIOWrite,

For line 191, consider typing result based on the possible return types from the switch cases.

src/components/data-display/tables/DomainsTable.tsx (3)

57-65: Consolidate duplicate lucide-react imports.

The imports from lucide-react are split across two import statements (lines 57 and 58-65). Consolidate them for cleaner code.

🔎 Suggested fix
-import { Activity, StoreIcon } from 'lucide-react';
-import {
-  AlertTriangle,
-  CircleCheck,
-  Copy,
-  DollarSign,
-  ExternalLink,
-  Star,
-} from 'lucide-react';
+import {
+  Activity,
+  AlertTriangle,
+  CircleCheck,
+  Copy,
+  DollarSign,
+  ExternalLink,
+  Star,
+  StoreIcon,
+} from 'lucide-react';

288-371: Consider extracting modal state management to reduce duplication.

The RoleDisplay component and InterruptedWorkflowAction both manage showContinueWorkflowModal state with similar patterns. Consider creating a custom hook or using a render prop pattern to reduce duplication.

This is a minor improvement for maintainability when the pattern is used more broadly.


869-938: IIFE pattern works but could be cleaner.

The immediately-invoked function expression (IIFE) pattern on lines 869-938 works for the conditional rendering logic, but extracting this to a named helper component (similar to MarketplaceActionIcon) would improve readability and testability.

This is optional since the current implementation is functional and the component is already handling multiple concerns.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab92794 and 1743da4.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (8)
  • package.json
  • src/components/data-display/VerticalTimelineStepper.tsx
  • src/components/data-display/tables/DomainsTable.tsx
  • src/components/modals/ContinueWorkflowModal/ContinueWorkflowModal.tsx
  • src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx
  • src/components/modals/ListNameForSaleModal/panels/ConfirmListingPanel.tsx
  • src/components/modals/ListNameForSaleModal/panels/ProcessTransactionPanel.tsx
  • src/components/pages/Manage/Manage.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/modals/ListNameForSaleModal/panels/ConfirmListingPanel.tsx
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-02T17:38:52.889Z
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 899
File: src/services/turbo/BaseTokenPurchaseService.ts:0-0
Timestamp: 2025-12-02T17:38:52.889Z
Learning: In src/services/turbo/BaseTokenPurchaseService.ts, the function executeBaseTokenPurchase intentionally only handles Base token top-up to Turbo credits, not the actual ArNS purchase. The ArNS purchase is handled separately by dispatchArIOInteraction in Checkout.tsx because it manages multiple complex purchase workflows (lease, permabuy, extend, increase undernames, etc.) with their own state management. This separation of concerns is by design.

Applied to files:

  • src/components/pages/Manage/Manage.tsx
  • src/components/modals/ContinueWorkflowModal/ContinueWorkflowModal.tsx
📚 Learning: 2025-09-10T16:35:55.727Z
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 873
File: src/components/modals/ant-management/UpgradeDomainsModal/UpgradeDomainsModal.tsx:0-0
Timestamp: 2025-09-10T16:35:55.727Z
Learning: In arns-react, when calling buildDomainInfoQuery (src/hooks/useDomainInfo.tsx), you must pass arioProcessId along with arioContract, domain, aoNetwork, hyperbeamUrl, and antRegistryProcessId; omitting arioProcessId can lead to "No ANT id or record found" and break upgrades.

Applied to files:

  • src/components/pages/Manage/Manage.tsx
  • src/components/data-display/tables/DomainsTable.tsx
  • src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx
📚 Learning: 2025-09-10T16:35:55.727Z
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 873
File: src/components/modals/ant-management/UpgradeDomainsModal/UpgradeDomainsModal.tsx:0-0
Timestamp: 2025-09-10T16:35:55.727Z
Learning: In arns-react, when calling buildDomainInfoQuery (src/hooks/useDomainInfo.tsx), you must pass arioProcessId along with arioContract, domain, aoNetwork, hyperbeamUrl, and antRegistryProcessId; omitting arioProcessId can lead to “No ANT id or record found” and break upgrades.

Applied to files:

  • src/components/pages/Manage/Manage.tsx
  • src/components/data-display/tables/DomainsTable.tsx
  • src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx
📚 Learning: 2025-12-11T05:20:18.825Z
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 902
File: src/components/modals/turbo/panels/crypto/CryptoConfirmation.tsx:536-540
Timestamp: 2025-12-11T05:20:18.825Z
Learning: Guideline: For tokens on Base network that use 6 decimals (e.g., base-ario ERC-20), convert amount to smallest units using a 1e6 multiplier. Use Math.round(amount * 1e6) when computing the on-chain value. Do not assume 18 decimals. Apply this conversion consistently in code paths that perform payments in CryptoConfirmation.tsx or similar components, and document that the base-ario/USDC-like tokens use 6 decimals to avoid mispricing or transfer errors.

Applied to files:

  • src/components/pages/Manage/Manage.tsx
  • src/components/data-display/VerticalTimelineStepper.tsx
  • src/components/modals/ListNameForSaleModal/panels/ProcessTransactionPanel.tsx
  • src/components/data-display/tables/DomainsTable.tsx
  • src/components/modals/ContinueWorkflowModal/ContinueWorkflowModal.tsx
  • src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx
🧬 Code graph analysis (3)
src/components/pages/Manage/Manage.tsx (1)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (178-179)
src/components/modals/ListNameForSaleModal/panels/ProcessTransactionPanel.tsx (1)
src/components/data-display/VerticalTimelineStepper.tsx (1)
  • VerticalTimelineStepper (3-41)
src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx (10)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (178-179)
src/state/contexts/WalletState.tsx (1)
  • useWalletState (49-50)
src/hooks/useArIOPrice.ts (1)
  • useArIoPrice (9-32)
src/hooks/useMarketplaceInfo.ts (1)
  • useMarketplaceInfo (4-25)
src/hooks/useMarketplaceUserAssets.ts (1)
  • useMarketplaceUserAssets (44-61)
src/utils/network.ts (1)
  • queryClient (39-45)
src/hooks/useANTIntent.ts (1)
  • useANTIntent (17-75)
src/components/layout/index.ts (1)
  • Loader (12-12)
src/utils/searchUtils/searchUtils.ts (1)
  • sleep (128-130)
src/services/arweave/ArweaveTransactionID.ts (1)
  • ArweaveTransactionID (7-31)
🔇 Additional comments (12)
src/components/pages/Manage/Manage.tsx (3)

103-122: Marketplace integration in refresh flow looks good.

The addition of marketplaceProcessId to the dispatchArNSUpdate call is consistent with the marketplace functionality being added. The parameter follows the same pattern as other process IDs already in use.


59-60: Minor styling refactor - no functional changes.

The change from styled wrapper to class-based styling simplifies the markup without affecting behavior.


21-29: Verify if dispatchArNSUpdate handles optional or undefined marketplaceProcessId.

The newly added marketplaceProcessId is destructured from global state but its optionality in the dispatchArNSUpdate function signature requires confirmation. Verify the function signature to ensure it either marks marketplaceProcessId as optional (with ?) or provides a default value, and confirm that undefined values are handled gracefully rather than causing runtime errors.

src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx (3)

1-48: LGTM on imports and type definitions.

The imports are well-organized and the type definitions for ListingType and PanelStates are clear and appropriately exported for use by other components.


114-150: Fee calculation logic looks correct.

The fee calculation using SDK functions calculateListingFee and calculateSaleTax with proper mARIO/ARIO conversions is well implemented. The separation of listing fee (paid upfront) from sale tax (deducted from proceeds) is correctly handled.


179-280: Well-structured workflow step management.

The updateWorkflowSteps callback provides a clean abstraction for updating step states with appropriate icons. The switch statements are comprehensive and handle all defined states.

src/components/modals/ListNameForSaleModal/panels/ProcessTransactionPanel.tsx (1)

1-70: LGTM!

Clean presentational component with clear state-based rendering. The exported ProcessTransactionPanelProps type enables proper typing for consumers, and the conditional UI logic for complete/error states is straightforward.

src/components/modals/ContinueWorkflowModal/ContinueWorkflowModal.tsx (1)

51-68: Well-structured state management from global context.

Good use of destructuring to extract only the needed values from global and wallet state hooks.

src/components/data-display/tables/DomainsTable.tsx (4)

126-204: LGTM on ErrorStateTooltip component.

Well-structured error handling component with clipboard copy functionality and Discord reporting. The error config object captures relevant debugging information for support.


438-452: Helper functions are well-designed.

isMarketplaceCompatible and getInterruptedWorkflowForDomain are clean helper functions that improve readability of the table rendering logic.


1081-1111: LGTM on modal integrations.

The modal rendering for ListNameForSaleModal and UpgradeDomainForMarketplaceModal is well-implemented with proper state management. The onUpgradeComplete callback pattern for chaining the upgrade-to-sale flow is a nice UX touch.


476-489: Role detection with marketplace ownership is well-implemented.

The getRoleWithMarketplace function correctly prioritizes marketplace ownership check before falling back to the standard ownership status logic.

createAoSigner,
mARIOToken,
} from '@ar.io/sdk';
import { connect } from '@permaweb/aoconnect';
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 | 🟡 Minor

Unused import.

connect is imported from @permaweb/aoconnect but never used in this file.

-import { connect } from '@permaweb/aoconnect';
📝 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
import { connect } from '@permaweb/aoconnect';
🤖 Prompt for AI Agents
In src/components/modals/ContinueWorkflowModal/ContinueWorkflowModal.tsx around
line 8, the import "connect" from "@permaweb/aoconnect" is unused; remove this
import to eliminate the dead code (or if it was intended to be used, wire it
into the component export or usage where appropriate). Ensure imports are
cleaned up and run the linter to confirm no remaining unused imports.

Comment on lines +221 to +232
const retries = 0;
const maxRetries = 10;
while (retries < maxRetries) {
try {
const _res = await marketplaceContract.getUserAssets({
address: walletAddress.toString(),
arioProcessId,
});
} catch {
console.error('Issue retrieving marketplace assets');
}
}
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

Critical: Infinite loop will freeze the application.

retries is declared as const and never incremented, causing the while loop to run forever. This will freeze the UI when the PUSH_INTENT workflow is executed.

🔎 Proposed fix
-          const retries = 0;
+          let retries = 0;
           const maxRetries = 10;
           while (retries < maxRetries) {
             try {
               const _res = await marketplaceContract.getUserAssets({
                 address: walletAddress.toString(),
                 arioProcessId,
               });
+              // Add success condition to break out of loop
+              break;
             } catch {
               console.error('Issue retrieving marketplace assets');
+              retries++;
+              await new Promise(resolve => setTimeout(resolve, 2000)); // Add delay between retries
             }
           }
📝 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
const retries = 0;
const maxRetries = 10;
while (retries < maxRetries) {
try {
const _res = await marketplaceContract.getUserAssets({
address: walletAddress.toString(),
arioProcessId,
});
} catch {
console.error('Issue retrieving marketplace assets');
}
}
let retries = 0;
const maxRetries = 10;
while (retries < maxRetries) {
try {
const _res = await marketplaceContract.getUserAssets({
address: walletAddress.toString(),
arioProcessId,
});
// Add success condition to break out of loop
break;
} catch {
console.error('Issue retrieving marketplace assets');
retries++;
await new Promise(resolve => setTimeout(resolve, 2000)); // Add delay between retries
}
}
🤖 Prompt for AI Agents
In src/components/modals/ContinueWorkflowModal/ContinueWorkflowModal.tsx around
lines 221 to 232, the retry loop is broken because retries is declared as const
and never incremented, causing an infinite loop; change retries to a mutable
variable (let), increment it on each iteration, exit the loop when the call
succeeds (break) or when retries >= maxRetries, and implement a small
delay/backoff between attempts (e.g., setTimeout/await sleep) and surface or
handle the final failure after max retries to avoid freezing the UI.

Comment on lines +310 to +324
<div className="flex items-center gap-3 text-sm">
<span className="text-grey whitespace-nowrap">ANT Owner:</span>
<ArweaveID
id={new ArweaveTransactionID(domainInfo?.state?.Owner || '')}
type={ArweaveIdTypes.ADDRESS}
characterCount={8}
/>
<ArrowRight className="w-3 h-3 text-grey" />
<span className="text-grey">Expected:</span>
<ArweaveID
id={new ArweaveTransactionID(marketplaceProcessId)}
type={ArweaveIdTypes.ADDRESS}
characterCount={8}
/>
</div>
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 | 🟠 Major

Potential crash if domainInfo?.state?.Owner is empty string.

Creating new ArweaveTransactionID('') will throw an error since the constructor validates the ID format. The conditional domainInfo?.state?.Owner || '' will pass an empty string when Owner is undefined.

🔎 Proposed fix
+              {domainInfo?.state?.Owner && (
                 <ArweaveID
                   id={new ArweaveTransactionID(domainInfo?.state?.Owner || '')}
                   type={ArweaveIdTypes.ADDRESS}
                   characterCount={8}
                 />
+              )}
+              {!domainInfo?.state?.Owner && <span className="text-grey">Unknown</span>}
📝 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
<div className="flex items-center gap-3 text-sm">
<span className="text-grey whitespace-nowrap">ANT Owner:</span>
<ArweaveID
id={new ArweaveTransactionID(domainInfo?.state?.Owner || '')}
type={ArweaveIdTypes.ADDRESS}
characterCount={8}
/>
<ArrowRight className="w-3 h-3 text-grey" />
<span className="text-grey">Expected:</span>
<ArweaveID
id={new ArweaveTransactionID(marketplaceProcessId)}
type={ArweaveIdTypes.ADDRESS}
characterCount={8}
/>
</div>
<div className="flex items-center gap-3 text-sm">
<span className="text-grey whitespace-nowrap">ANT Owner:</span>
{domainInfo?.state?.Owner && (
<ArweaveID
id={new ArweaveTransactionID(domainInfo?.state?.Owner || '')}
type={ArweaveIdTypes.ADDRESS}
characterCount={8}
/>
)}
{!domainInfo?.state?.Owner && <span className="text-grey">Unknown</span>}
<ArrowRight className="w-3 h-3 text-grey" />
<span className="text-grey">Expected:</span>
<ArweaveID
id={new ArweaveTransactionID(marketplaceProcessId)}
type={ArweaveIdTypes.ADDRESS}
characterCount={8}
/>
</div>
🤖 Prompt for AI Agents
In src/components/modals/ContinueWorkflowModal/ContinueWorkflowModal.tsx around
lines 310 to 324, the code constructs new
ArweaveTransactionID(domainInfo?.state?.Owner || '') which will pass an empty
string to the constructor and can throw; change it to only instantiate
ArweaveTransactionID when Owner is a non-empty value (e.g., use a ternary or
conditional render: if domainInfo?.state?.Owner is truthy then create new
ArweaveTransactionID(domainInfo.state.Owner) and render ArweaveID, otherwise
skip rendering the ArweaveID or pass undefined/null if ArweaveID accepts that),
ensuring you do not pass an empty string into the ArweaveTransactionID
constructor.

Comment on lines +380 to +385
if (
tries >= maxTries ||
newARIOBalance <= feeDetails.listingFee
) {
throw new Error('Failed to deposit enough ARIO');
}
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 | 🟠 Major

Logic error in deposit verification loop.

The condition newARIOBalance <= feeDetails.listingFee on line 382 will throw an error even when the balance equals the listing fee, which should be a valid state. Additionally, the error is thrown when tries >= maxTries regardless of whether the balance check passed.

🔎 Proposed fix
                              if (
-                               tries >= maxTries ||
-                               newARIOBalance <= feeDetails.listingFee
+                               newARIOBalance < feeDetails.listingFee &&
+                               tries >= maxTries
                              ) {
                                throw new Error('Failed to deposit enough ARIO');
                              }
+                             if (newARIOBalance >= feeDetails.listingFee) {
+                               break; // Exit the loop on success
+                             }
🤖 Prompt for AI Agents
In src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx around
lines 380 to 385, the current condition throws even when balance equals the
listing fee and also throws on max tries regardless of balance; change the logic
so the error is thrown only when both maxTries reached AND the balance is
strictly less than the required listingFee (use <, not <=), i.e. check tries >=
maxTries && newARIOBalance < feeDetails.listingFee, otherwise allow success when
balance >= listingFee or continue retrying until maxTries.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx (1)

385-390: Logic error in deposit verification loop.

The condition throws an error even when the balance equals the listing fee (using <= instead of <). Additionally, the error is thrown when tries >= maxTries regardless of whether the balance check passed. The loop never explicitly breaks on success.

🔎 Proposed fix (from previous review)
                               if (
-                                tries >= maxTries ||
-                                newARIOBalance <= feeDetails.listingFee
+                                newARIOBalance < feeDetails.listingFee &&
+                                tries >= maxTries
                               ) {
                                 throw new Error('Failed to deposit enough ARIO');
                               }
+                              if (newARIOBalance >= feeDetails.listingFee) {
+                                break; // Exit the loop on success
+                              }
🧹 Nitpick comments (4)
src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx (4)

316-318: Document or make configurable the 30-day expiration limit.

The 30-day maximum expiration period is hardcoded without explanation. Consider documenting why this limit exists (marketplace policy, technical constraint, etc.) or making it configurable via marketplace settings.

💡 Suggested documentation
       if (expirationDate.getTime() > Date.now() + 1000 * 60 * 60 * 24 * 30) {
-        throw new Error('Expiration date must be within next 30 days');
+        throw new Error('Expiration date must be within next 30 days'); // Marketplace policy limit
       }

Alternatively, retrieve this from marketplace info if available:

const maxExpirationDays = marketplaceInfo?.maxListingDays ?? 30;

494-508: Consider more precise query invalidation.

The current predicates reset all queries containing 'marketplace', domainName, or antId. This might invalidate more queries than necessary. Consider using specific query keys instead.

💡 Suggested improvement
             // we need to reset the query states for orders and ant state.
-            queryClient.resetQueries({
-              predicate: (query) =>
-                query.queryKey.some(
-                  (key: unknown) =>
-                    typeof key === 'string' && key.startsWith('marketplace'),
-                ),
-            });
-            queryClient.resetQueries({
-              predicate: (query) =>
-                query.queryKey.some(
-                  (key: unknown) =>
-                    (typeof key === 'string' && key.includes(domainName)) ||
-                    (typeof key === 'string' && key.includes(antId ?? '')),
-                ),
-            });
+            // Invalidate specific marketplace queries
+            queryClient.invalidateQueries({ 
+              queryKey: ['marketplace-intents', marketplaceProcessId] 
+            });
+            queryClient.invalidateQueries({ 
+              queryKey: ['marketplace-orders'] 
+            });
+            
+            // Invalidate domain and ANT info
+            if (antId) {
+              queryClient.invalidateQueries({ queryKey: ['ant', antId] });
+              queryClient.invalidateQueries({ queryKey: ['domain', domainName] });
+            }

Using invalidateQueries instead of resetQueries is also preferred, as it refetches rather than clearing the cache.


552-552: Add optional chaining for consistency.

While the code is safe due to the enclosing conditional on line 525, using optional chaining here aligns with the null-safety pattern and makes the code more robust to future refactoring.

🔎 Proposed change
                 <ArweaveID
-                  id={new ArweaveTransactionID(result.antTransferResult.id)}
+                  id={new ArweaveTransactionID(result.antTransferResult?.id ?? '')}
                   type={ArweaveIdTypes.INTERACTION}
                   shouldLink
                   characterCount={8}
                 />

Note: This assumes ArweaveTransactionID constructor handles empty strings gracefully. Based on the relevant code snippet, it throws an error for invalid IDs, so you may need to conditionally render the ArweaveID component instead:

-                <span>
+                {result.antTransferResult?.id && (
+                  <span>
                     View transfer on aolink{' '}
                     <ArweaveID
-                      id={new ArweaveTransactionID(result.antTransferResult.id)}
+                      id={new ArweaveTransactionID(result.antTransferResult.id)}
                       type={ArweaveIdTypes.INTERACTION}
                       shouldLink
                       characterCount={8}
                     />
-                </span>
+                  </span>
+                )}

577-577: Remove redundant query invalidation.

This query invalidation on line 577 is redundant with the specific query resets performed on lines 494-508 within the success path. Consider removing this or consolidating the invalidation logic.

💡 Suggested change
-      queryClient.invalidateQueries({ queryKey: ['marketplace'] });
     } catch (error) {
       setWorkflowComplete(true);
       setWorkflowError(true);
       eventEmitter.emit('error', error);
     } finally {
       setIsLoading(false);
     }

If you need to ensure queries are invalidated even on error, move this to the finally block and remove the earlier invalidations:

     } catch (error) {
       setWorkflowComplete(true);
       setWorkflowError(true);
       eventEmitter.emit('error', error);
     } finally {
       setIsLoading(false);
+      // Invalidate queries whether success or error
+      queryClient.invalidateQueries({ queryKey: ['marketplace'] });
     }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1743da4 and fcf0c32.

📒 Files selected for processing (1)
  • src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-10T16:35:55.727Z
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 873
File: src/components/modals/ant-management/UpgradeDomainsModal/UpgradeDomainsModal.tsx:0-0
Timestamp: 2025-09-10T16:35:55.727Z
Learning: In arns-react, when calling buildDomainInfoQuery (src/hooks/useDomainInfo.tsx), you must pass arioProcessId along with arioContract, domain, aoNetwork, hyperbeamUrl, and antRegistryProcessId; omitting arioProcessId can lead to “No ANT id or record found” and break upgrades.

Applied to files:

  • src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx
📚 Learning: 2025-09-10T16:35:55.727Z
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 873
File: src/components/modals/ant-management/UpgradeDomainsModal/UpgradeDomainsModal.tsx:0-0
Timestamp: 2025-09-10T16:35:55.727Z
Learning: In arns-react, when calling buildDomainInfoQuery (src/hooks/useDomainInfo.tsx), you must pass arioProcessId along with arioContract, domain, aoNetwork, hyperbeamUrl, and antRegistryProcessId; omitting arioProcessId can lead to "No ANT id or record found" and break upgrades.

Applied to files:

  • src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx
📚 Learning: 2025-12-11T05:20:18.825Z
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 902
File: src/components/modals/turbo/panels/crypto/CryptoConfirmation.tsx:536-540
Timestamp: 2025-12-11T05:20:18.825Z
Learning: Guideline: For tokens on Base network that use 6 decimals (e.g., base-ario ERC-20), convert amount to smallest units using a 1e6 multiplier. Use Math.round(amount * 1e6) when computing the on-chain value. Do not assume 18 decimals. Apply this conversion consistently in code paths that perform payments in CryptoConfirmation.tsx or similar components, and document that the base-ario/USDC-like tokens use 6 decimals to avoid mispricing or transfer errors.

Applied to files:

  • src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx
🧬 Code graph analysis (1)
src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx (6)
src/hooks/useArIOPrice.ts (1)
  • useArIoPrice (9-32)
src/hooks/useMarketplaceInfo.ts (1)
  • useMarketplaceInfo (4-25)
src/hooks/useMarketplaceUserAssets.ts (1)
  • useMarketplaceUserAssets (44-61)
src/hooks/useANTIntent.ts (1)
  • useANTIntent (17-75)
src/utils/searchUtils/searchUtils.ts (1)
  • sleep (128-130)
src/services/arweave/ArweaveTransactionID.ts (1)
  • ArweaveTransactionID (7-31)

Comment on lines +68 to +82
deposit: {
title: 'Deposit Listing Fee',
description: 'Deposit the listing fee to the marketplace',
icon: <DollarSign className="w-4 h-4" />,
},
createIntent: {
title: 'Create Intent',
description: 'Create an intent to list the name for sale',
icon: <DollarSign className="w-4 h-4" />,
},
transferANT: {
title: 'Transfer ANT',
description: 'Transfer the ANT to the marketplace',
icon: <DollarSign className="w-4 h-4" />,
},
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 | 🟡 Minor

Icon inconsistency in default workflow steps.

All workflow steps initially use DollarSign icon, but createIntent (line 76) and transferANT (line 81) should use their respective icons (Handshake and Send) from the start. While updateWorkflowSteps corrects these icons when the workflow runs, any initial rendering before updates will display incorrect icons.

🔎 Proposed fix
   createIntent: {
     title: 'Create Intent',
     description: 'Create an intent to list the name for sale',
-    icon: <DollarSign className="w-4 h-4" />,
+    icon: <Handshake className="w-4 h-4" />,
   },
   transferANT: {
     title: 'Transfer ANT',
     description: 'Transfer the ANT to the marketplace',
-    icon: <DollarSign className="w-4 h-4" />,
+    icon: <Send className="w-4 h-4" />,
   },
🤖 Prompt for AI Agents
In src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx around
lines 68 to 82, the default workflow steps set all icons to DollarSign which
causes createIntent and transferANT to render incorrect icons initially; update
the createIntent icon to use the Handshake component and the transferANT icon to
use the Send component in the initial steps object, and ensure Handshake and
Send are imported at the top of the file so the correct icons display before
updateWorkflowSteps runs.

Comment on lines +351 to +354
const difference = Math.max(
1,
Math.round(feeDetails.listingFee - existingBalance),
).toString();
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 | 🟠 Major

Use Math.ceil instead of Math.round for deposit amount.

Math.round() can round down, potentially depositing less than required. For example, if the difference is 1.4 ARIO, Math.round(1.4) returns 1, which is insufficient. Use Math.ceil() to always round up.

🔎 Proposed fix
         const difference = Math.max(
           1,
-          Math.round(feeDetails.listingFee - existingBalance),
+          Math.ceil(feeDetails.listingFee - existingBalance),
         ).toString();
📝 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
const difference = Math.max(
1,
Math.round(feeDetails.listingFee - existingBalance),
).toString();
const difference = Math.max(
1,
Math.ceil(feeDetails.listingFee - existingBalance),
).toString();
🤖 Prompt for AI Agents
In src/components/modals/ListNameForSaleModal/ListNameForSaleModal.tsx around
lines 351 to 354, the deposit difference uses Math.round which can under-deposit
(e.g., 1.4 → 1); change the calculation to use Math.ceil so the difference is
always rounded up, and keep the Math.max(1, ...) guard and .toString()
conversion unchanged.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/components/pages/ViewListing/ViewListing.tsx (5)

239-266: Enhance deposit confirmation polling with better reliability.

The balance polling logic has several areas for improvement:

  1. Fixed 5-second intervals: No exponential backoff could lead to unnecessary load during temporary network issues.
  2. Silent error handling: Line 262 logs balance check errors but doesn't inform the user of transient issues.
  3. No progress feedback: Users wait up to 50 seconds without updates during the polling loop.
🔎 Proposed improvements
           // Wait for deposit to be confirmed
           let newBalance = 0;
           let tries = 0;
           const maxTries = 10;
+          const baseDelay = 3000; // Start with 3s
           while (newBalance < orderPrice && tries < maxTries) {
             try {
               const balanceResult =
                 await marketplaceContract.getMarketplaceBalance({
                   address: walletAddress.toString(),
                 });
               newBalance = new mARIOToken(Number(balanceResult?.balance ?? 0))
                 .toARIO()
                 .valueOf();
 
               if (newBalance >= orderPrice) break;
+              
+              // Update user on progress
+              updateWorkflowSteps({
+                step: 'deposit',
+                status: 'processing',
+                description: `Confirming deposit... (${tries + 1}/${maxTries})`,
+              });
             } catch (error) {
               console.error('Error checking balance:', error);
             }
             tries++;
-            await sleep(5000);
+            // Exponential backoff: 3s, 6s, 12s, capped at 15s
+            const delay = Math.min(baseDelay * Math.pow(2, tries - 1), 15000);
+            await sleep(delay);
           }

320-333: Query invalidation strategy may be overly broad.

Lines 320-332 use resetQueries with predicate functions that search for processId or name in query keys. This approach:

  • Could inadvertently reset unrelated queries if keys happen to contain these strings
  • May not be comprehensive if some related queries use different key structures

Consider using more specific query key patterns if the query keys follow a consistent structure:

// Instead of predicate-based matching
queryClient.resetQueries({ queryKey: ['domainInfo', name] });
queryClient.resetQueries({ queryKey: ['ant', domainInfo.processId] });
queryClient.resetQueries({ queryKey: ['ant-info', domainInfo.processId] });
queryClient.invalidateQueries({ queryKey: ['marketplace'] });
queryClient.invalidateQueries({ queryKey: ['marketplace-order', domainInfo.processId] });
queryClient.invalidateQueries({ queryKey: ['marketplace-user-assets', walletAddress] });

This is more explicit and reduces the risk of unintended cache invalidations.


556-564: Manual address truncation instead of using ArweaveID component.

Lines 556-564 manually truncate the seller address with string slicing, while other places use the ArweaveID component. This creates inconsistency in how addresses are displayed.

For consistency with the rest of the component, consider using the ArweaveID component here as well:

🔎 Proposed change
                     <a
                       href={`https://aolink.ar-io.dev/#/entity/${orderData.creator}`}
                       target="_blank"
                       rel="noopener noreferrer"
                       className="text-blue-400 hover:text-blue-300 transition-colors flex items-center gap-1 justify-end"
                     >
-                      {orderData.creator.slice(0, 8)}...
-                      {orderData.creator.slice(-8)}
+                      <ArweaveID
+                        id={new ArweaveTransactionID(orderData.creator)}
+                        type={ArweaveIdTypes.ADDRESS}
+                        shouldLink={false}
+                        characterCount={8}
+                      />
                       <ExternalLink className="w-3 h-3" />
                     </a>

Note: You'll need to adjust this to avoid double-linking, or remove the outer <a> tag if ArweaveID handles the link.


393-401: Loading state could use Loader component.

The loading state displays plain text instead of using the imported Loader component, which would provide a more polished and consistent loading experience.

🔎 Proposed improvement
   if (isLoading) {
     return (
       <div className="page text-white">
         <div className="flex justify-center items-center min-h-[400px]">
-          <div className="text-white text-xl">Loading listing details...</div>
+          <div className="flex flex-col items-center gap-4">
+            <Loader size={40} />
+            <div className="text-white text-xl">Loading listing details...</div>
+          </div>
         </div>
       </div>
     );
   }

113-121: Use consistent mARIO-to-ARIO conversion method for balance comparison.

The balance check uses two different conversion APIs from different packages:

  • Line 116–118: new mARIOToken(...).toARIO().valueOf() (from @ar.io/sdk) for user balance
  • Line 119: mARIOToTokenAmount(...) (from @ardrive/turbo-sdk) for order price

While both convert mARIO to ARIO, standardizing on a single method throughout the component would improve clarity and reduce maintenance risk. Consider using mARIOToken consistently since it's already imported and used elsewhere in the codebase.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fcf0c32 and a459b86.

📒 Files selected for processing (1)
  • src/components/pages/ViewListing/ViewListing.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-11T05:20:18.825Z
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 902
File: src/components/modals/turbo/panels/crypto/CryptoConfirmation.tsx:536-540
Timestamp: 2025-12-11T05:20:18.825Z
Learning: Guideline: For tokens on Base network that use 6 decimals (e.g., base-ario ERC-20), convert amount to smallest units using a 1e6 multiplier. Use Math.round(amount * 1e6) when computing the on-chain value. Do not assume 18 decimals. Apply this conversion consistently in code paths that perform payments in CryptoConfirmation.tsx or similar components, and document that the base-ario/USDC-like tokens use 6 decimals to avoid mispricing or transfer errors.

Applied to files:

  • src/components/pages/ViewListing/ViewListing.tsx
🧬 Code graph analysis (1)
src/components/pages/ViewListing/ViewListing.tsx (15)
src/components/icons/index.ts (2)
  • CheckIcon (89-89)
  • ArNSLogo (72-72)
src/components/pages/index.ts (1)
  • ViewListing (18-18)
src/state/contexts/GlobalState.tsx (1)
  • useGlobalState (178-179)
src/state/contexts/WalletState.tsx (1)
  • useWalletState (49-50)
src/hooks/useDomainInfo.tsx (1)
  • useDomainInfo (206-255)
src/hooks/useMarketplaceOrder.tsx (1)
  • useMarketplaceOrder (44-56)
src/hooks/useArIOPrice.ts (1)
  • useArIoPrice (9-32)
src/hooks/useMarketplaceUserAssets.ts (1)
  • useMarketplaceUserAssets (44-61)
src/utils/searchUtils/searchUtils.ts (2)
  • sleep (128-130)
  • decodeDomainToASCII (48-52)
src/utils/network.ts (1)
  • queryClient (39-45)
src/services/arweave/ArweaveTransactionID.ts (1)
  • ArweaveTransactionID (7-31)
src/components/data-display/AntLogoIcon.tsx (1)
  • AntLogoIcon (7-57)
src/utils/common/common.tsx (1)
  • formatARIOWithCommas (215-219)
src/components/layout/index.ts (1)
  • Loader (12-12)
src/components/data-display/VerticalTimelineStepper.tsx (1)
  • VerticalTimelineStepper (3-41)
🔇 Additional comments (9)
src/components/pages/ViewListing/ViewListing.tsx (9)

1-41: LGTM!

The imports are well-organized and all appear necessary for the marketplace listing functionality.


43-66: LGTM!

The workflow step configuration is clear and provides good user feedback structure for the multi-step purchase process.


68-112: LGTM!

The component initialization and state management are well-structured. The isExpired check correctly compares the current time against the order expiration.


123-180: LGTM!

The updateWorkflowSteps function provides clear visual feedback with appropriate icons and styling for each workflow state.


233-236: Deposit amount calculation looks correct.

The calculation Math.max(1, Math.ceil(orderPrice - existingBalance)) appropriately:

  • Ensures at least 1 ARIO is deposited (prevents zero deposits)
  • Rounds up fractional amounts to cover the full order price
  • Accounts for existing balance to avoid over-depositing

628-654: LGTM! Buy button state handling is comprehensive.

The buy button correctly handles all states:

  • Disabled when expired or already processing
  • Shows appropriate icons and text for each state
  • Uses proper styling for visual feedback

656-698: LGTM! Workflow UI provides excellent user feedback.

The processing workflow UI effectively:

  • Shows real-time progress through the VerticalTimelineStepper
  • Provides clear messaging for success and failure states
  • Offers appropriate actions (retry on error, view name on success)
  • Warns users not to close the page during processing

239-244: Add timeout handling for the purchase transaction.

The buyFixedPriceANT call at line 300 lacks any timeout mechanism. If the transaction hangs indefinitely, users remain stuck in the processing state with no recourse except refreshing the page.

The deposit call (line 239) has implicit timeout protection via its polling loop (10 retries × 5-second intervals), but the purchase call has none.

Wrap the purchase transaction with explicit timeout handling:

async function withTimeout<T>(
  promise: Promise<T>,
  timeoutMs: number,
  errorMessage: string
): Promise<T> {
  const timeout = new Promise<never>((_, reject) =>
    setTimeout(() => reject(new Error(errorMessage)), timeoutMs)
  );
  return Promise.race([promise, timeout]);
}

// Usage at line 300:
const result = await withTimeout(
  marketplaceContract.buyFixedPriceANT({
    antId: domainInfo.processId,
  }),
  60000, // 60 second timeout
  'Purchase transaction timed out'
);
⛔ Skipped due to learnings
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 902
File: src/components/modals/turbo/panels/crypto/CryptoConfirmation.tsx:536-540
Timestamp: 2025-12-11T05:20:30.374Z
Learning: The Base ARIO (base-ario) ERC-20 token contract on Base network uses 6 decimals, not 18 decimals. When converting base-ario amounts to smallest units for payment transactions, use 1e6 multiplier (Math.round(amount * 1e6)), consistent with USDC handling.

427-428: Decimal precision for ARIO conversions is accurate.

Mainnet ARIO has a denomination of 6, and each ARIO is divisible into 1,000,000 µARIO, confirming 6 decimal precision. The code correctly uses mARIOToTokenAmount from turbo-sdk to convert from mARIO (10^-6 ARIO) to ARIO, and formatARIOWithCommas appropriately displays the result with up to 3 decimal places. The base-ario ERC-20 token also uses 6 decimals, consistent with mainnet ARIO. No precision issues detected.

ao: aoClient,
}),
signer: createAoSigner(wallet.contractSigner),
ario: arioContract as AoARIOWrite,
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 | 🟠 Major

Type assertion bypasses type safety.

The type assertion arioContract as AoARIOWrite on Line 215 could hide potential type mismatches. If arioContract doesn't fully satisfy the AoARIOWrite interface, this could lead to runtime errors.

🔎 Verify type compatibility or add validation

Option 1: Add runtime validation:

+      if (!arioContract || typeof arioContract.depositArIO !== 'function') {
+        throw new Error('Invalid ARIO contract instance');
+      }
       const marketplaceContract = new ArNSMarketplaceWrite({
         process: new AOProcess({
           processId: marketplaceProcessId,
           ao: aoClient,
         }),
         signer: createAoSigner(wallet.contractSigner),
         ario: arioContract as AoARIOWrite,
       });

Option 2: Verify that arioContract from global state is properly typed as AoARIOWrite throughout the application to eliminate the need for this assertion.

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

🤖 Prompt for AI Agents
In src/components/pages/ViewListing/ViewListing.tsx around line 215, the inline
type assertion "arioContract as AoARIOWrite" bypasses compile-time safety;
instead ensure arioContract is correctly typed at its source (e.g., update the
global state/type definitions or the selector that provides arioContract to
return AoARIOWrite) or add a small runtime type guard/validation that checks
required methods/properties before using it and only then narrow the type;
remove the unsafe "as" cast and either rely on the corrected source typing or
perform the guard and narrow the type with a safe conditional.

Comment on lines 483 to 487
id={new ArweaveTransactionID(orderData.creator)}
type={ArweaveIdTypes.ADDRESS}
shouldLink={true}
characterCount={12}
/>
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 | 🟠 Major

ArweaveTransactionID constructor can throw on invalid input.

The ArweaveTransactionID constructor throws an error if the provided ID doesn't match the expected 43-character format. If orderData.creator, domainInfo.processId, or orderData.id contain invalid values, this will cause unhandled exceptions in the render phase, crashing the component.

Add validation before constructing ArweaveTransactionID instances:

🔎 Proposed defensive rendering
                 {orderData?.creator && (
                   <div className="flex items-center justify-between gap-2 text-grey text-sm">
                     <span>Owner:</span>
                     <div className="flex items-end justify-end">
+                      {(() => {
+                        try {
+                          return (
                             <ArweaveID
                               id={new ArweaveTransactionID(orderData.creator)}
                               type={ArweaveIdTypes.ADDRESS}
                               shouldLink={true}
                               characterCount={12}
                             />
+                          );
+                        } catch {
+                          return <span className="text-white font-mono">{orderData.creator}</span>;
+                        }
+                      })()}
                     </div>
                   </div>
                 )}

Apply similar try-catch wrappers for Lines 505-509 (ANT Process) and Lines 617-621 (Order ID).

Alternatively, create a safe wrapper component:

function SafeArweaveID({ id, ...props }: { id: string } & Omit<ComponentProps<typeof ArweaveID>, 'id'>) {
  try {
    return <ArweaveID id={new ArweaveTransactionID(id)} {...props} />;
  } catch {
    return <span className="text-white font-mono">{id}</span>;
  }
}

Also applies to: 505-509, 617-621

🤖 Prompt for AI Agents
In src/components/pages/ViewListing/ViewListing.tsx around lines 483-487,
constructing new ArweaveTransactionID(orderData.creator) can throw for invalid
IDs; wrap that construction with a validation or try/catch and fall back to
rendering the raw string (or a safe placeholder) so render errors are not
thrown; apply the same defensive pattern to the other instances mentioned (lines
505-509 for domainInfo.processId and 617-621 for orderData.id), or replace
usages with a small SafeArweaveID wrapper component that catches construction
errors and returns a safe fallback.

<LeaseDurationFromEndTimestamp
endTimestamp={
domainInfo?.arnsRecord?.type === 'lease'
? (domainInfo.arnsRecord as any)?.endTimestamp
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 | 🟠 Major

Type assertion bypasses type safety for lease record.

Line 519 uses (domainInfo.arnsRecord as any)?.endTimestamp to access the endTimestamp property. This bypasses TypeScript's type checking and could lead to runtime errors if the structure changes.

🔎 Use proper type guard instead
                  <span className="text-white">
                    <LeaseDurationFromEndTimestamp
                      endTimestamp={
-                       domainInfo?.arnsRecord?.type === 'lease'
-                         ? (domainInfo.arnsRecord as any)?.endTimestamp
-                         : undefined // Permabuy domains or no record - let component show "Permanent"
+                       domainInfo?.arnsRecord?.type === 'lease' && 
+                       'endTimestamp' in domainInfo.arnsRecord
+                         ? domainInfo.arnsRecord.endTimestamp
+                         : undefined
                      }
                    />
                  </span>

Or better, update the type definition to properly discriminate the union type based on the type field.

📝 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
? (domainInfo.arnsRecord as any)?.endTimestamp
<span className="text-white">
<LeaseDurationFromEndTimestamp
endTimestamp={
domainInfo?.arnsRecord?.type === 'lease' &&
'endTimestamp' in domainInfo.arnsRecord
? domainInfo.arnsRecord.endTimestamp
: undefined
}
/>
</span>
🤖 Prompt for AI Agents
In src/components/pages/ViewListing/ViewListing.tsx around line 519, the code
uses `(domainInfo.arnsRecord as any)?.endTimestamp`, which bypasses TypeScript
safety; replace this with a proper type-narrowing approach: add a type guard
(e.g., function isLeaseRecord(r): r is LeaseRecord { return r?.type === 'lease'
&& 'endTimestamp' in r }) or refine the domainInfo/arnsRecord union type so you
can safely access endTimestamp only when arnsRecord is the lease variant, then
use that guard before reading endTimestamp and handle the non-lease case
explicitly (fallback/undefined) instead of casting to any.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/components/pages/ViewListing/ViewListing.tsx (1)

320-333: Prefer explicit query key patterns over string matching.

The query invalidation logic uses string matching on query keys, which is fragile. If query key structures change (e.g., from strings to structured objects), this will silently fail.

🔎 Proposed refactor using explicit query keys

Consider defining query key factories in your hooks and using them consistently:

// In your hooks file or a shared queryKeys file
export const queryKeys = {
  domain: (name: string) => ['domain', name] as const,
  marketplaceOrder: (antId: string) => ['marketplace', 'order', antId] as const,
  marketplaceUserAssets: () => ['marketplace', 'user-assets'] as const,
};

Then update invalidation:

-      queryClient.resetQueries({
-        predicate: (query) =>
-          query.queryKey.some(
-            (key: unknown) =>
-              typeof key === 'string' && key.includes(domainInfo.processId),
-          ),
-      });
-      queryClient.resetQueries({
-        predicate: (query) =>
-          query.queryKey.some(
-            (key: unknown) => typeof key === 'string' && key.includes(name),
-          ),
-      });
-      queryClient.invalidateQueries({ queryKey: ['marketplace'] });
+      // Invalidate domain-specific queries
+      queryClient.invalidateQueries({ 
+        queryKey: queryKeys.domain(name) 
+      });
+      // Invalidate order queries
+      queryClient.invalidateQueries({ 
+        queryKey: queryKeys.marketplaceOrder(domainInfo.processId) 
+      });
+      // Invalidate user assets
+      queryClient.invalidateQueries({ 
+        queryKey: queryKeys.marketplaceUserAssets() 
+      });
+      // Invalidate all marketplace listings
+      queryClient.invalidateQueries({ 
+        queryKey: ['marketplace'] 
+      });

This approach is type-safe, explicit, and won't break silently if query key structures evolve.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a459b86 and 8b033e6.

📒 Files selected for processing (1)
  • src/components/pages/ViewListing/ViewListing.tsx
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-02T17:38:52.889Z
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 899
File: src/services/turbo/BaseTokenPurchaseService.ts:0-0
Timestamp: 2025-12-02T17:38:52.889Z
Learning: In src/services/turbo/BaseTokenPurchaseService.ts, the function executeBaseTokenPurchase intentionally only handles Base token top-up to Turbo credits, not the actual ArNS purchase. The ArNS purchase is handled separately by dispatchArIOInteraction in Checkout.tsx because it manages multiple complex purchase workflows (lease, permabuy, extend, increase undernames, etc.) with their own state management. This separation of concerns is by design.

Applied to files:

  • src/components/pages/ViewListing/ViewListing.tsx
📚 Learning: 2025-09-10T16:35:55.727Z
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 873
File: src/components/modals/ant-management/UpgradeDomainsModal/UpgradeDomainsModal.tsx:0-0
Timestamp: 2025-09-10T16:35:55.727Z
Learning: In arns-react, when calling buildDomainInfoQuery (src/hooks/useDomainInfo.tsx), you must pass arioProcessId along with arioContract, domain, aoNetwork, hyperbeamUrl, and antRegistryProcessId; omitting arioProcessId can lead to “No ANT id or record found” and break upgrades.

Applied to files:

  • src/components/pages/ViewListing/ViewListing.tsx
📚 Learning: 2025-09-10T16:35:55.727Z
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 873
File: src/components/modals/ant-management/UpgradeDomainsModal/UpgradeDomainsModal.tsx:0-0
Timestamp: 2025-09-10T16:35:55.727Z
Learning: In arns-react, when calling buildDomainInfoQuery (src/hooks/useDomainInfo.tsx), you must pass arioProcessId along with arioContract, domain, aoNetwork, hyperbeamUrl, and antRegistryProcessId; omitting arioProcessId can lead to "No ANT id or record found" and break upgrades.

Applied to files:

  • src/components/pages/ViewListing/ViewListing.tsx
📚 Learning: 2025-12-11T05:20:18.825Z
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 902
File: src/components/modals/turbo/panels/crypto/CryptoConfirmation.tsx:536-540
Timestamp: 2025-12-11T05:20:18.825Z
Learning: Guideline: For tokens on Base network that use 6 decimals (e.g., base-ario ERC-20), convert amount to smallest units using a 1e6 multiplier. Use Math.round(amount * 1e6) when computing the on-chain value. Do not assume 18 decimals. Apply this conversion consistently in code paths that perform payments in CryptoConfirmation.tsx or similar components, and document that the base-ario/USDC-like tokens use 6 decimals to avoid mispricing or transfer errors.

Applied to files:

  • src/components/pages/ViewListing/ViewListing.tsx
🔇 Additional comments (1)
src/components/pages/ViewListing/ViewListing.tsx (1)

81-87: No issue. The useDomainInfo hook's public signature only accepts domain and antId; callers cannot supply arioProcessId. The call at line 85 with { domain: name } is correct as implemented.

Comment on lines +246 to +266
// Wait for deposit to be confirmed
let newBalance = 0;
let tries = 0;
const maxTries = 10;
while (newBalance < orderPrice && tries < maxTries) {
try {
const balanceResult =
await marketplaceContract.getMarketplaceBalance({
address: walletAddress.toString(),
});
newBalance = new mARIOToken(Number(balanceResult?.balance ?? 0))
.toARIO()
.valueOf();

if (newBalance >= orderPrice) break;
} catch (error) {
console.error('Error checking balance:', error);
}
tries++;
await sleep(5000);
}
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 | 🟠 Major

Add cleanup for balance polling to prevent memory leaks.

The balance polling loop doesn't check if the component is still mounted. If the user navigates away during the purchase flow, the loop continues executing and may attempt state updates on an unmounted component.

🔎 Recommended fix using AbortController pattern
  async function handleBuy() {
+   const abortController = new AbortController();
    setIsBuying(true);
    setShowProcessing(true);
    setWorkflowComplete(false);
    setWorkflowError(false);
    setWorkflowSteps(defaultBuyWorkflowSteps);

    try {
      // ... existing validation ...

      if (!hasSufficientBalance) {
        updateWorkflowSteps({ step: 'deposit', status: 'processing' });

        // ... deposit logic ...

        // Wait for deposit to be confirmed
        let newBalance = 0;
        let tries = 0;
        const maxTries = 10;
-       while (newBalance < orderPrice && tries < maxTries) {
+       while (newBalance < orderPrice && tries < maxTries && !abortController.signal.aborted) {
          try {
            const balanceResult =
              await marketplaceContract.getMarketplaceBalance({
                address: walletAddress.toString(),
              });
            newBalance = new mARIOToken(Number(balanceResult?.balance ?? 0))
              .toARIO()
              .valueOf();

            if (newBalance >= orderPrice) break;
          } catch (error) {
            console.error('Error checking balance:', error);
          }
          tries++;
          await sleep(5000);
        }

+       if (abortController.signal.aborted) {
+         throw new Error('Purchase cancelled');
+       }
+
        if (newBalance < orderPrice) {
          throw new Error('Failed to deposit enough ARIO');
        }
        // ... rest of logic ...
      }
    } catch (error) {
      console.error(error);
      setWorkflowComplete(true);
      setWorkflowError(true);
      eventEmitter.emit('error', error);
    } finally {
      setIsBuying(false);
    }
  }

Then add cleanup in a useEffect:

useEffect(() => {
  const controller = new AbortController();
  return () => controller.abort();
}, []);

Or consider moving the entire purchase flow to a custom hook that properly manages lifecycle.

📝 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
// Wait for deposit to be confirmed
let newBalance = 0;
let tries = 0;
const maxTries = 10;
while (newBalance < orderPrice && tries < maxTries) {
try {
const balanceResult =
await marketplaceContract.getMarketplaceBalance({
address: walletAddress.toString(),
});
newBalance = new mARIOToken(Number(balanceResult?.balance ?? 0))
.toARIO()
.valueOf();
if (newBalance >= orderPrice) break;
} catch (error) {
console.error('Error checking balance:', error);
}
tries++;
await sleep(5000);
}
const abortControllerRef = useRef<AbortController | null>(null);
async function handleBuy() {
abortControllerRef.current = new AbortController();
setIsBuying(true);
setShowProcessing(true);
setWorkflowComplete(false);
setWorkflowError(false);
setWorkflowSteps(defaultBuyWorkflowSteps);
try {
// ... existing validation ...
if (!hasSufficientBalance) {
updateWorkflowSteps({ step: 'deposit', status: 'processing' });
// ... deposit logic ...
// Wait for deposit to be confirmed
let newBalance = 0;
let tries = 0;
const maxTries = 10;
while (newBalance < orderPrice && tries < maxTries && !abortControllerRef.current?.signal.aborted) {
try {
const balanceResult =
await marketplaceContract.getMarketplaceBalance({
address: walletAddress.toString(),
});
newBalance = new mARIOToken(Number(balanceResult?.balance ?? 0))
.toARIO()
.valueOf();
if (newBalance >= orderPrice) break;
} catch (error) {
console.error('Error checking balance:', error);
}
tries++;
await sleep(5000);
}
if (abortControllerRef.current?.signal.aborted) {
throw new Error('Purchase cancelled');
}
if (newBalance < orderPrice) {
throw new Error('Failed to deposit enough ARIO');
}
// ... rest of logic ...
}
} catch (error) {
console.error(error);
setWorkflowComplete(true);
setWorkflowError(true);
eventEmitter.emit('error', error);
} finally {
setIsBuying(false);
}
}
useEffect(() => {
return () => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, []);
🤖 Prompt for AI Agents
In src/components/pages/ViewListing/ViewListing.tsx around lines 246 to 266, the
balance-polling loop can continue running after the component unmounts causing
memory leaks and possible state updates on an unmounted component; fix by wiring
an AbortController (or a mounted flag) into the loop: create the controller in a
surrounding useEffect and return a cleanup that calls controller.abort(), pass
controller.signal or check controller.signal.aborted (or the mounted flag)
inside the while loop and break/return when aborted, and guard any state updates
so they only run when not aborted/unmounted; alternatively move the purchase
flow into a custom hook that accepts and respects an AbortSignal and performs
the same cleanup.

Comment on lines +335 to +351
eventEmitter.emit('success', {
name: 'Buy ANT',
message: (
<span>
Successfully bought {name}!
<br />
<br />
<span>View transaction on aolink </span>
<ArweaveID
id={new ArweaveTransactionID(result.id)}
type={ArweaveIdTypes.INTERACTION}
characterCount={8}
shouldLink={true}
/>
</span>
),
});
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 | 🟠 Major

Add defensive error handling for ArweaveTransactionID construction.

Line 344 constructs new ArweaveTransactionID(result.id) within the success event message without validation. If result.id is invalid or malformed, this will throw an error and prevent the success message from being displayed, degrading the user experience.

🔎 Proposed defensive rendering
      eventEmitter.emit('success', {
        name: 'Buy ANT',
        message: (
          <span>
            Successfully bought {name}!
            <br />
            <br />
-           <span>View transaction on aolink </span>
-           <ArweaveID
-             id={new ArweaveTransactionID(result.id)}
-             type={ArweaveIdTypes.INTERACTION}
-             characterCount={8}
-             shouldLink={true}
-           />
+           {result?.id ? (
+             <>
+               <span>View transaction on aolink </span>
+               {(() => {
+                 try {
+                   return (
+                     <ArweaveID
+                       id={new ArweaveTransactionID(result.id)}
+                       type={ArweaveIdTypes.INTERACTION}
+                       characterCount={8}
+                       shouldLink={true}
+                     />
+                   );
+                 } catch (error) {
+                   console.warn('Invalid transaction ID:', result.id);
+                   return <span className="font-mono">{result.id}</span>;
+                 }
+               })()}
+             </>
+           ) : null}
          </span>
        ),
      });

Alternatively, validate result.id format before constructing the ArweaveTransactionID, or add a safe wrapper utility function.

📝 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
eventEmitter.emit('success', {
name: 'Buy ANT',
message: (
<span>
Successfully bought {name}!
<br />
<br />
<span>View transaction on aolink </span>
<ArweaveID
id={new ArweaveTransactionID(result.id)}
type={ArweaveIdTypes.INTERACTION}
characterCount={8}
shouldLink={true}
/>
</span>
),
});
eventEmitter.emit('success', {
name: 'Buy ANT',
message: (
<span>
Successfully bought {name}!
<br />
<br />
{result?.id ? (
<>
<span>View transaction on aolink </span>
{(() => {
try {
return (
<ArweaveID
id={new ArweaveTransactionID(result.id)}
type={ArweaveIdTypes.INTERACTION}
characterCount={8}
shouldLink={true}
/>
);
} catch (error) {
console.warn('Invalid transaction ID:', result.id);
return <span className="font-mono">{result.id}</span>;
}
})()}
</>
) : null}
</span>
),
});
🤖 Prompt for AI Agents
In src/components/pages/ViewListing/ViewListing.tsx around lines 335 to 351,
constructing new ArweaveTransactionID(result.id) directly inside the success
message can throw if result.id is missing or malformed; wrap the construction in
a small validation/try-catch (or use a safe helper) that verifies result.id is a
valid transaction id before creating ArweaveTransactionID, and if validation
fails fallback to rendering a plain transaction id/link or omit the ArweaveID
component while logging the error for telemetry so the success message still
displays reliably.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
package.json (2)

26-26: Alpha version still in use (duplicate concern).

This was previously flagged: using ^1.35.0-alpha.2 introduces instability risk. Consider switching to the latest stable release or pinning the exact alpha version if you require newer features.


36-37: Sentry version mismatch still present (duplicate concern).

This was previously flagged: @sentry/browser at ^10.29.0 and @sentry/react at ^7.45.0 are incompatible major versions. Update both packages to use the same major version to avoid runtime conflicts.

🧹 Nitpick comments (1)
package.json (1)

60-60: Consider using caret range for consistency.

lucide-react is pinned to an exact version 0.556.0 without a caret, which is inconsistent with other dependencies. Consider using ^0.556.0 to allow patch and minor updates, unless the exact pinning is intentional to prevent breaking changes.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b033e6 and 129f3c6.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (1)
  • package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: lint_and_build
🔇 Additional comments (1)
package.json (1)

23-23: Verify the necessity of using an alpha version.

@ar.io/sdk is set to ^3.23.0-alpha.3, which introduces instability risk. The latest stable release is 3.19.0; alpha releases can have breaking changes, and the caret operator allows auto-upgrading to newer alpha versions. Ensure this alpha version is required for specific features; otherwise, use the stable release or pin the exact alpha version.

Comment on lines +27 to +29
"@dha-team/arbundles": "^1.0.4",
"@permaweb/ao-scheduler-utils": "^0.0.29",
"@permaweb/aoconnect": "0.0.68",
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 | 🟠 Major

🧩 Analysis chain

🌐 Web query:

@permaweb/ao-scheduler-utils npm package stability production ready

💡 Result:

Short answer: Not production-ready. Use for experimentation only.

Why:

  • Package is pre-1.0 (v0.0.27) and published under the permaweb/ao monorepo which is described as an actively changing testnet-phase project (BSL/experimental). [1][2].
  • npm shows modest usage (≈1.7k weekly downloads) and recent releases but no 1.x stable marker or clear OSS license on the package page. [1][3].
  • The GitHub monorepo indicates the stack is still under active development and “no guarantees or core support” for self-hosting; project guidance describes it as testnet/experimental. [2].

Recommendation:

  • OK to adopt for development, prototypes, or internal tooling that can tolerate breaking changes.
  • Avoid for high‑risk production systems unless you vendor/pin a specific commit, add tests, and are prepared to maintain/fix issues yourself.
  • Before deploying, review the repo’s issues/PRs, test coverage, recent commits, and confirm the exact license for your use case. [2][4].

Sources:
[1] npm package page for @permaweb/ao-scheduler-utils.
[2] permaweb/ao GitHub repository (monorepo README & notes about testnet/dev).
[3] Libraries.io / Socket.dev package metadata and security/maintenance summaries.


🌐 Web query:

@permaweb/aoconnect npm package version history stability

💡 Result:

Summary — @permaweb/aoconnect stability and version history

  • Package: @permaweb/aoconnect (successor to deprecated @permaweb/ao-connect). [1][2]
  • Release activity: frequent small releases in the 0.0.x line; recent publishes within days/weeks (npm shows recent publish and latest metadata). [1][3]
  • Latest published version (reported by Snyk/npm during checks): 0.0.92 (Snyk) / npm listing shows 0.0.90 with a recent publish stamp — confirm exact latest with npm before install. [3][1]
  • Popularity/maintenance signals: modest weekly downloads (thousands) and an active repo under permaweb org; package is maintained and not deprecated. [1][2]
  • Migration note: older package @permaweb/ao-connect was deprecated in favor of @permaweb/aoconnect. [4]

If you want I can:

  • fetch the exact latest version and full changelog now from the npm registry and the GitHub releases (recommended to confirm).

Consider the experimental status of these dependencies and evaluate whether outdated pinned versions are appropriate.

Both @permaweb/ao-scheduler-utils (^0.0.29) and @permaweb/aoconnect (0.0.68) are part of the permaweb/ao testnet-phase project and explicitly designated as experimental, not production-ready. The pinned version of @permaweb/aoconnect appears outdated (0.0.68 vs. available 0.0.90+); evaluate whether updating to the latest 0.0.x release is necessary for security or bug fixes, and confirm the team understands the stability risks of adopting experimental dependencies.

🤖 Prompt for AI Agents
In package.json around lines 27-29, the review flags that
@permaweb/ao-scheduler-utils (^0.0.29) and @permaweb/aoconnect (0.0.68) are
experimental and the aoconnect pin appears outdated; inspect the packages'
changelogs/release notes for security/bug fixes since 0.0.68, run the project's
test suite and smoke tests against the latest 0.0.x release (or the most recent
safe patch, e.g., bump aoconnect to ^0.0.90+ if fixes are relevant), decide
whether to relax or tighten the version spec (use caret for receiving patches or
pin if stability is required), update package.json accordingly, regenerate the
lockfile, run full CI, and add a brief comment in the repo or PR noting the
experimental risk so the team acknowledges the stability trade-offs.

"lottie-react": "^2.4.0",
"lucide-react": "^0.438.0",
"lucide-react": "0.556.0",
"permaweb-deploy": "^2.5.1",
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 | 🟠 Major

Move deployment tool to devDependencies.

permaweb-deploy is a deployment tool used only in build scripts (lines 8-9), not in runtime code. It should be in devDependencies rather than dependencies to avoid unnecessarily increasing the production bundle size and installation footprint.

🔎 Proposed fix

Move permaweb-deploy from dependencies to devDependencies:

     "lucide-react": "0.556.0",
-    "permaweb-deploy": "^2.5.1",
     "plimit-lit": "^3.0.1",

And add it to devDependencies section (around line 93):

     "http-server": "^14.1.1",
     "husky": "^9.1.7",
+    "permaweb-deploy": "^2.5.1",
     "lint-staged": "^13.1.0",
📝 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
"permaweb-deploy": "^2.5.1",
// Dependencies section (with permaweb-deploy removed):
"lucide-react": "0.556.0",
"plimit-lit": "^3.0.1",
// DevDependencies section (with permaweb-deploy added):
"http-server": "^14.1.1",
"husky": "^9.1.7",
"permaweb-deploy": "^2.5.1",
"lint-staged": "^13.1.0",
🤖 Prompt for AI Agents
In package.json around line 61, permaweb-deploy is listed under "dependencies"
but is only used by build/deploy scripts; remove the "permaweb-deploy": "^2.5.1"
entry from "dependencies" and add the same version string under
"devDependencies" (around line ~93) so it’s installed only for development/build
environments; after editing, run your package manager (npm/yarn/pnpm) to update
node_modules and the lockfile so CI and installs reflect the change.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/data-display/tables/DomainsTable.tsx (1)

519-525: Missing dependencies in useEffect may cause stale table data.

The effect uses walletAddress (lines 463, 472, 487), latestAntVersion (line 473), and marketplaceProcessId (line 480), but these are not included in the dependency array. This can cause:

  • Table not updating when user connects/disconnects wallet
  • Stale role calculations when marketplace process ID changes
  • Incorrect compatibility status with stale ANT version
🔎 Proposed fix
   }, [
     domainData,
     loading,
     loadingArnsState,
     primaryNameData,
     dispatchArNSState,
+    walletAddress,
+    latestAntVersion,
+    marketplaceProcessId,
   ]);
🧹 Nitpick comments (1)
src/components/data-display/tables/DomainsTable.tsx (1)

289-372: Consider extracting duplicated modal logic.

The ContinueWorkflowModal rendering pattern is duplicated across multiple conditional branches (lines 321-329, 357-365). This could be extracted into a shared pattern.

🔎 Suggested refactor
 function RoleDisplay({
   role,
   domainName,
   processId,
 }: {
   role: string;
   domainName: string;
   processId: string;
 }) {
   const { hasIntent, intent } = useANTIntent(processId);
   const [showContinueWorkflowModal, setShowContinueWorkflowModal] =
     useState(false);
 
+  const renderModal = () =>
+    showContinueWorkflowModal && intent && (
+      <ContinueWorkflowModal
+        show={showContinueWorkflowModal}
+        onClose={() => setShowContinueWorkflowModal(false)}
+        domainName={domainName}
+        antId={processId}
+        intentId={intent.intentId}
+      />
+    );
+
   if (role === 'marketplace') {
     if (hasIntent && intent) {
       return (
         <>
           <div className="flex items-center gap-2">
             <button
               onClick={() => setShowContinueWorkflowModal(true)}
               className="relative flex items-center gap-2 hover:opacity-80 transition-opacity"
             >
               {/* ... */}
             </button>
           </div>
-          {showContinueWorkflowModal && (
-            <ContinueWorkflowModal
-              show={showContinueWorkflowModal}
-              onClose={() => setShowContinueWorkflowModal(false)}
-              domainName={domainName}
-              antId={processId}
-              intentId={intent.intentId}
-            />
-          )}
+          {renderModal()}
         </>
       );
     }
     // ... apply similarly to other branches
   }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 129f3c6 and 9636ed7.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (4)
  • package.json
  • src/components/data-display/tables/DomainsTable.tsx
  • src/components/pages/Register/Checkout.tsx
  • src/utils/constants.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • package.json
  • src/components/pages/Register/Checkout.tsx
  • src/utils/constants.ts
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-10T16:35:55.727Z
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 873
File: src/components/modals/ant-management/UpgradeDomainsModal/UpgradeDomainsModal.tsx:0-0
Timestamp: 2025-09-10T16:35:55.727Z
Learning: In arns-react, when calling buildDomainInfoQuery (src/hooks/useDomainInfo.tsx), you must pass arioProcessId along with arioContract, domain, aoNetwork, hyperbeamUrl, and antRegistryProcessId; omitting arioProcessId can lead to "No ANT id or record found" and break upgrades.

Applied to files:

  • src/components/data-display/tables/DomainsTable.tsx
📚 Learning: 2025-09-10T16:35:55.727Z
Learnt from: dtfiedler
Repo: ar-io/arns-react PR: 873
File: src/components/modals/ant-management/UpgradeDomainsModal/UpgradeDomainsModal.tsx:0-0
Timestamp: 2025-09-10T16:35:55.727Z
Learning: In arns-react, when calling buildDomainInfoQuery (src/hooks/useDomainInfo.tsx), you must pass arioProcessId along with arioContract, domain, aoNetwork, hyperbeamUrl, and antRegistryProcessId; omitting arioProcessId can lead to “No ANT id or record found” and break upgrades.

Applied to files:

  • src/components/data-display/tables/DomainsTable.tsx
📚 Learning: 2025-12-11T05:20:18.825Z
Learnt from: vilenarios
Repo: ar-io/arns-react PR: 902
File: src/components/modals/turbo/panels/crypto/CryptoConfirmation.tsx:536-540
Timestamp: 2025-12-11T05:20:18.825Z
Learning: Guideline: For tokens on Base network that use 6 decimals (e.g., base-ario ERC-20), convert amount to smallest units using a 1e6 multiplier. Use Math.round(amount * 1e6) when computing the on-chain value. Do not assume 18 decimals. Apply this conversion consistently in code paths that perform payments in CryptoConfirmation.tsx or similar components, and document that the base-ario/USDC-like tokens use 6 decimals to avoid mispricing or transfer errors.

Applied to files:

  • src/components/data-display/tables/DomainsTable.tsx
🧬 Code graph analysis (1)
src/components/data-display/tables/DomainsTable.tsx (6)
src/utils/constants.ts (1)
  • ARIO_DISCORD_LINK (251-251)
src/hooks/useANTIntent.ts (1)
  • useANTIntent (17-75)
src/hooks/useMarketplaceOrder.tsx (1)
  • useMarketplaceOrder (44-56)
src/hooks/useInterruptedWorkflows.ts (2)
  • InterruptedWorkflow (6-10)
  • useInterruptedWorkflows (15-67)
src/utils/common/common.tsx (1)
  • formatForMaxCharCount (134-148)
src/components/modals/index.ts (2)
  • ListNameForSaleModal (15-15)
  • UpgradeDomainForMarketplaceModal (16-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: deploy-preview
  • GitHub Check: lint_and_build
🔇 Additional comments (6)
src/components/data-display/tables/DomainsTable.tsx (6)

7-66: LGTM!

The new imports are well-organized and support the marketplace integration. The separation between component imports, hooks, and icons is clear.


127-205: LGTM!

Error state tooltip is well-implemented with proper event handling, clipboard API error handling, and secure external link opening.


406-453: LGTM!

The marketplace-related state management and helper functions follow existing patterns and are cleanly implemented.


607-632: LGTM!

The enhanced name column rendering with tooltip and external link is well-structured.


870-939: LGTM!

The action column logic correctly handles the different marketplace states:

  • Marketplace-owned domains without interrupted workflows → marketplace icon
  • Domains with interrupted workflows → continue workflow action
  • Other domains → list for sale/upgrade flow

The conditional flow is clear and the upgrade-to-marketplace path correctly chains to the listing modal.


1082-1113: LGTM!

The modal integrations correctly handle the upgrade-to-marketplace flow, chaining the sale modal after successful upgrade.

Comment on lines +207 to +253
// Helper component to determine the correct icon for marketplace-owned domains
function MarketplaceActionIcon({
domainName,
processId,
}: { domainName: string; processId: string }) {
const { hasIntent } = useANTIntent(processId);

const { data: order, error: orderError } = useMarketplaceOrder({
antId: processId,
});

// If there's an intent but no order (or order fetch failed), show Activity icon
const hasOrder = order && !orderError;

// Error state: no intent and no order (marketplace owns ANT but nothing exists)
if (!hasIntent && !hasOrder) {
return <ErrorStateTooltip domainName={domainName} antId={processId} />;
}

if (hasIntent && !hasOrder) {
return (
<Tooltip
message="Pending marketplace activity"
icon={
<div className="flex items-center justify-center w-[18px] h-[18px] text-orange-400 hover:text-orange-300 transition-colors">
<Activity className="w-[18px] h-[18px]" />
</div>
}
/>
);
}

// Default to marketplace store icon
return (
<Tooltip
message="View in Marketplace"
icon={
<Link
to={`/marketplace/names/${domainName}`}
className="flex items-center justify-center w-[18px] h-[18px] text-blue-400 hover:text-blue-300 transition-colors"
>
<StoreIcon className="w-[18px] h-[18px]" />
</Link>
}
/>
);
}
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 | 🟡 Minor

Consider loading states before showing error tooltip.

When useANTIntent or useMarketplaceOrder are still loading, their data will be undefined/false, which could cause the error state (line 222) to show prematurely before the queries complete.

🔎 Proposed fix
 function MarketplaceActionIcon({
   domainName,
   processId,
 }: { domainName: string; processId: string }) {
-  const { hasIntent } = useANTIntent(processId);
+  const { hasIntent, isLoading: intentLoading } = useANTIntent(processId);
 
-  const { data: order, error: orderError } = useMarketplaceOrder({
+  const { data: order, error: orderError, isLoading: orderLoading } = useMarketplaceOrder({
     antId: processId,
   });
 
+  // Don't show error state while still loading
+  if (intentLoading || orderLoading) {
+    return null;
+  }
+
   // If there's an intent but no order (or order fetch failed), show Activity icon
   const hasOrder = order && !orderError;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants