Skip to content

Conversation

rodrigooler
Copy link
Collaborator

@rodrigooler rodrigooler commented Sep 26, 2025

Summary by CodeRabbit

  • New Features

    • Complete Simple Bridge flow: new page and UI for cross‑chain transfers (dual‑wallet connect, amount input with fraction buttons, transaction progress dialog with detailed steps).
  • Refactor

    • Responsive sidebar/navigation and unified multi‑chain wallet balances; selection dialog highlights a recommended “Simple Bridge” option.
  • Style/UX

    • Updated asset/icon paths, trigger label changed to “Bridge options menu”, and a fixed APR bar added to layouts.
  • Removal

    • Wallet conflict detection overlay removed.
  • Documentation/Chores

    • ESLint rule adjusted and YAML loader/config replaced with JSON-based configs.

- Introduced the Simple Bridge feature, allowing users to transfer TORUS tokens between Base and Native chains in a streamlined process.
- Added components for wallet connection, transaction progress, and user interface enhancements.
- Implemented hooks for managing dual wallet connections and orchestrated transfers, ensuring a smooth user experience during token transfers.

This update enhances the functionality of the Torus Bridge application, providing users with a more efficient way to manage their TORUS tokens.
- Disable the "react-hooks/preserve-manual-memoization" rule to allow for more flexibility in hook usage.
- This change aims to streamline the linting process and reduce unnecessary warnings related to manual memoization in React components.
- Removed unused components (GuideDialog and SelectActionDialog) from the Simple Bridge page to streamline the UI.
- Updated the SEO description formatting for better readability.
- This change enhances the overall clarity and performance of the Simple Bridge application.
- Introduced new SVG icons for Base and Native balance displays in the Torus Bridge application.
- Updated the WalletBalance component to utilize the new icons and improved the layout for better user experience.
- Added FractionButtons component to streamline the selection of transfer fractions in the Simple Bridge form.
- Enhanced the Simple Bridge form to display wallet addresses and improved overall styling for clarity.

This update enhances the visual representation and functionality of the Torus Bridge application, providing users with a more intuitive interface for managing their balances and transfers.
- Introduced new SVG icons for Base, EVM, and Native balances in the Torus Bridge application.
- Updated the WalletBalance component to utilize the new icons, enhancing the visual representation of user balances.
- Modified the Simple Bridge form to incorporate new icon paths for improved clarity.
- Adjusted the logo URI in the chains configuration to point to the new logo asset.

This update enhances the user interface of the Torus Bridge application, providing a more intuitive experience for managing balances and transfers.
- Reformatted SVG icon files in the Torus Bridge application for better code clarity and consistency.
- Adjusted path elements to enhance visual structure and maintainability.

This update improves the overall quality of the SVG assets used in the application.
…eading

- Integrated token formatting using `formatToken` for improved balance display in the Simple Bridge form.
- Replaced the balance fetching method with `useReadContract` to read ERC20 token balances directly from the contract.
- Updated balance calculations to ensure accurate representation of available funds for transfers.

This update improves the user experience by providing clearer balance information and ensuring accurate token management in the Torus Bridge application.
- Deleted the ProgressStepper component from the Simple Bridge application to streamline the codebase.
- Updated exports in the SimpleBridge index file to reflect the removal of the ProgressStepper.

This change simplifies the Simple Bridge component structure and improves maintainability.
- Refactored the Sidebar component to improve code organization and readability.
- Introduced a new SidebarOption component for better encapsulation of sidebar link logic.
- Utilized useMemo and useCallback hooks to optimize performance and manage active link states.
- Updated the rendering logic to dynamically generate sidebar options based on the current path.

This update streamlines the Sidebar component, enhancing maintainability and user experience in the Torus Bridge application.
- Deleted the TransferDetails component from the Simple Bridge page to streamline the UI.
- This change enhances the overall clarity and performance of the Simple Bridge application.
- Introduced a new state, STEP_2_SWITCHING, to the SimpleBridgeStep enum to enhance the transaction workflow.
- Updated the SimpleBridgeTransaction interface to include a more detailed status field, allowing for better tracking of transaction states.

This update improves the clarity and functionality of the Simple Bridge application, providing users with a more comprehensive view of transaction progress.
- Added a TransactionLifecycleDialog to the SimpleBridgeForm for improved user feedback during transfers.
- Implemented state management for showing the dialog based on transfer progress and errors.
- Refactored address and balance formatting functions for better readability and maintainability.
- Updated button text and placeholder logic to enhance user experience.

This update provides users with clearer transaction status and improves the overall functionality of the Simple Bridge application.
…back

- Added a new TransactionLifecycleDialog component to the Simple Bridge application to provide users with real-time updates on transaction progress.
- Integrated lifecycle steps for both Base to Native and Native to Base transfers, improving user experience during the bridging process.
- Enhanced state management to handle various transaction statuses, including error handling and retry options.

This update significantly improves user feedback and clarity during token transfers in the Simple Bridge application.
Copy link

linear bot commented Sep 26, 2025

WEB-353 Fast Bridge UI

Copy link
Contributor

coderabbitai bot commented Sep 26, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a new Simple Bridge feature (UI, page, hooks, flows, types) for dual-direction transfers; refactors sidebar/navigation and wallet balance UI; migrates YAML configs to JSON; adds polling/chain-switch helpers and orchestration hooks; updates APR UI to prop-driven/client variants; removes wallet-conflict guard; adjusts lint/build config and asset paths.

Changes

Cohort / File(s) Summary
Simple Bridge feature (UI, page, hooks, flows, types)
apps/torus-bridge/src/app/simple/page.tsx, apps/torus-bridge/src/app/simple/_components/*, apps/torus-bridge/src/app/simple/hooks/*, apps/torus-bridge/src/app/simple/_components/...-transaction-lifecycle-dialog.tsx, apps/torus-bridge/src/app/simple/_components/...-transaction-step-item.tsx, apps/torus-bridge/src/app/simple/_components/simple-bridge-*.tsx, apps/torus-bridge/src/app/simple/hooks/simple-bridge-*.ts, apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts
Adds SimpleBridge page and components (SimpleBridgeForm, DualWalletConnector, FractionButtons, TransactionLifecycleDialog, TransactionStepItem), types, shared state hook, orchestrator hook, lifecycle hook, chain-switch, polling, and concrete base↔native flow implementations.
SidebarLinks refactor
apps/torus-bridge/src/app/_components/shared/sidebar-links.tsx
Replaces hard-coded nav with data-driven SidebarOption + responsive Select, active-state tracking via pathname subscription, and Suspense wrapper.
Wallet balance enhancements
apps/torus-bridge/src/app/_components/shared/wallet-balance.tsx
Reworks balance UI to support multiple chains, adds BalanceIcon, fetches balances via wagmi/useReadContract/useBalance, and uses contractAddresses config.
Action dialog updates
apps/torus-bridge/src/app/_components/select-action-dialog.tsx
Adds optional priority?: boolean to SelectCardProps, conditional prioritized styling and a “Simple Bridge (Recommended)” prioritized entry; updates icon asset paths and trigger label.
Asset / chain config changes
apps/torus-bridge/src/app/_components/transfer-evm.tsx, apps/torus-bridge/src/consts/chains.ts, apps/torus-bridge/src/consts/chains.json, apps/torus-bridge/src/consts/chains.yaml
Moves Torus chain metadata from YAML to JSON, removes YAML entry, updates chain logo and icon asset paths to /assets/icons/....
Warp routes / config format changes
apps/torus-bridge/src/consts/warp-routes.json, apps/torus-bridge/src/consts/warp-routes.yaml, apps/torus-bridge/src/app/_components/warp-core-config.ts
Adds warp-routes.json, removes warp-routes.yaml, and updates warp-core-config parsing to consume JSON.
Config additions
apps/torus-bridge/src/config.ts
Adds exported contractAddresses mapping (per env) containing base.torusErc20.
Helpers, polling, chain-switch modules
apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts, .../simple-bridge-polling.ts, .../simple-bridge-chain-switch.ts
Adds GAS/POLLING/TIMEOUT constants, UserRejectedError, withTimeout/isUserRejectionError/getExplorerUrl/formatErrorForUser, pollBalanceUntilTarget/pollEvmBalance, and switchChainWithRetry + throwOnChainSwitchFailure.
Flow implementations
apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts, .../simple-bridge-native-to-base-flow.ts
Implements step1/step2 functions for base→native and native→base flows with chain switching, signing, polling, transaction tracking, and explicit error handling.
Orchestration & shared state hooks
apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-orchestrated-transfer.ts, .../use-simple-bridge-shared-state.ts, .../use-simple-bridge-dual-wallet.ts, .../use-transaction-lifecycle-steps.ts
Adds useOrchestratedTransfer, useSimpleBridgeSharedState, useDualWallet, useTransactionLifecycleSteps for orchestration, state management, and lifecycle derivation.
Token/transfer tweaks
apps/torus-bridge/src/app/_components/tokens/token-list-modal.tsx, apps/torus-bridge/src/app/_components/transfer-token/_components/transfer-token-form.tsx, apps/torus-bridge/src/hooks/use-token-transfer.ts
Removes ESLint suppression comments; changes useTokenTransfer API to accept throwOnError?: boolean, makes executeTransfer async and return last tx hash, and adjusts error propagation.
Removed wallet conflict guard
apps/torus-bridge/src/app/_components/wallet-conflict-guard.tsx, apps/torus-bridge/src/app/layout.tsx
Deletes WalletConflictGuard and related detection/hooks/UI; layout now injects APRBarClient instead.
Build / lint / dependency changes
tooling/eslint/react.js, apps/torus-bridge/next.config.mjs, apps/torus-bridge/package.json
Disables react-hooks/preserve-manual-memoization lint rule; removes YAML loader turbopack/webpack config and deletes yaml-loader dependency.
Chains metadata loader update
apps/torus-bridge/src/app/_components/chains/chain-metadata.ts
Switches import to JSON (chains.json) and adjusts metadata assembly.
Docs
CLAUDE.md
Adds guideline discouraging eslint-disable (duplicated section).
Transaction step UI
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-step-item.tsx
Adds TransactionStepItem component to render step status, signature warnings, errors, and explorer links.
UI input component
packages/ui/src/components/input.tsx
Introduces InputReadonly component and exports; refactors InputProps to exported interface.
APR bar / UI library changes
packages/ui/src/components/apr-*, packages/ui/package.json
Splits APRBar into prop-driven APRBar + APRBarClient, adds TorusToUSD and hooks, updates exports and dependencies.
Misc: tailwind, env types, layouts
packages/ui/tailwind.config.ts, apps/torus-wallet/next-env.d.ts, apps/torus-wallet/src/app/layout.tsx, apps/torus-wallet/src/app/staking/_components/...
Adds new Tailwind animations; adds triple-slash route types reference; swaps APRBar to APRBarClient and adapts useAPR to accept api param.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Form as SimpleBridgeForm
  participant Dual as DualWalletConnector
  participant Orchestrator as useOrchestratedTransfer
  participant ChainSwitch as switchChainWithRetry
  participant Hyperlane as Hyperlane
  participant Poll as pollBalanceUntilTarget / pollEvmBalance
  participant UI as TransactionLifecycleDialog

  Note over Form,Orchestrator: Base → Native (high-level)
  User->>Form: Submit(amount,direction)
  Form->>Dual: areWalletsReady?
  alt wallets not ready
    Form-->>User: Render DualWalletConnector
  else wallets ready
    Form->>UI: Open dialog (STEP_1_PREPARING)
    Form->>Orchestrator: executeTransfer(base-to-native, amount)
    Orchestrator->>ChainSwitch: ensure Base chain (retry)
    Orchestrator->>Hyperlane: trigger Base→TorusEVM transfer
    Orchestrator->>Poll: poll TorusEVM balance until increase
    Orchestrator->>ChainSwitch: switch to TorusEVM if needed
    Orchestrator->>Orchestrator: perform TorusEVM→Native withdrawal
    Orchestrator->>Poll: poll Native balance until increase
    Orchestrator->>Form: update steps & tx hashes
    Form->>UI: show final state (success/error)
  end
Loading
sequenceDiagram
  autonumber
  actor User
  participant Form as SimpleBridgeForm
  participant Orchestrator as useOrchestratedTransfer
  participant Substrate as Substrate (Native)
  participant Poll as pollBalanceUntilTarget
  participant Hyperlane as Hyperlane
  participant UI as TransactionLifecycleDialog

  Note over Form,Orchestrator: Native → Base (high-level)
  User->>Form: Submit(amount,direction)
  Form->>Orchestrator: executeTransfer(native-to-base, amount)
  Orchestrator->>Substrate: submit native transfer (finalization)
  Orchestrator->>Poll: poll TorusEVM balance after finalization
  Orchestrator->>Hyperlane: trigger TorusEVM→Base transfer
  Orchestrator->>Poll: poll Base balance until increase
  Orchestrator->>Form: update steps & tx hashes
  Form->>UI: show final state (success/error)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

Feature, needs-review

Suggested reviewers

  • steinerkelvin
  • EdSDR

Poem

I thump my whiskers, hop and bind,
Two ledgers meet — a bridge I find.
I nudge the buttons, steps align,
If retries rumble, I munch—refine.
Carrots crossed, success! A hop divine. 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The pull request title includes noisy prefixes (“wip: change:”) and a ticket reference, making it overly verbose and generic rather than a concise summary of the primary change. It does not clearly highlight the significant UX enhancements to the bridge flow, dynamic sidebar, and new components introduced in this changeset. Please remove the “wip” and ticket prefix and focus the title on the main UX improvements, for example: “Enhance Bridge UX with new Simple Bridge flow and dynamic sidebar”.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/web-353

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.

@rodrigooler rodrigooler changed the title Fix/web 353 change: Bridge - Enhance UX (web-353) Sep 26, 2025
@rodrigooler rodrigooler changed the title change: Bridge - Enhance UX (web-353) wip: change: Bridge - Enhance UX (web-353) Sep 29, 2025
- Refactored the DualWalletConnector component for improved readability and structure, including better state management for wallet connection statuses.
- Updated the SimpleBridgeForm to use a more descriptive reset function name.
- Enhanced transaction lifecycle handling in the TransactionLifecycleDialog, ensuring clearer status updates and error handling during transfers.
- Improved type definitions in SimpleBridgeTransaction to include metadata for better transaction tracking.

This update enhances user experience by providing clearer feedback during wallet connections and transaction processes in the Torus Bridge application.
- Removed unnecessary comments and streamlined the connection logic within the useDualWallet hook.
- Enhanced the connection state management by simplifying condition checks and reducing redundant code.
- Improved overall readability and maintainability of the hook, ensuring clearer logic flow for wallet connections.

This update enhances the clarity and efficiency of the useDualWallet hook in the Torus Bridge application.
…y and status management

- Simplified connection logic by removing redundant checks and enhancing condition handling.
- Introduced helper functions for status indicators and connection messages to improve clarity.
- Streamlined the rendering of wallet connection statuses, ensuring a more maintainable code structure.

This update enhances the user experience by providing clearer feedback on wallet connections in the Torus Bridge application.
…ty and maintainability

- Introduced a new interface for props in the FractionButton and FractionButtons components to enhance type safety and clarity.
- Simplified the rendering of fraction buttons by mapping over an array of fractions, reducing repetitive code.
- Improved state management for button selection and disabled states, ensuring a more efficient user experience.

This update enhances the clarity and maintainability of the FractionButtons component in the Torus Bridge application.
- Introduced the SimpleBridge component to encapsulate the SimpleBridgeForm, improving the structure of the Simple Bridge application.
- Added new components including SimpleBridgeForm, DualWalletConnector, FractionButtons, and TransactionLifecycleDialog to facilitate user interactions during token transfers.
- Enhanced state management and user feedback mechanisms throughout the bridging process, ensuring clearer transaction status updates and error handling.
- Implemented type definitions for better clarity and maintainability in the Simple Bridge workflow.

This update significantly improves the user experience by providing a more organized and interactive interface for token transfers in the Simple Bridge application.
- Removed redundant import statement for TransactionLifecycleDialog in the SimpleBridgeForm component, ensuring cleaner code structure.
- Added a newline at the end of the FractionButtons component file to adhere to coding standards.

This update improves code clarity and maintains consistency across the Simple Bridge application components.
…gement

- Introduced a new StepStatus type to standardize transaction step statuses across the TransactionLifecycleDialog component.
- Refactored lifecycle step creation into dedicated functions for Base to Native and Native to Base transfers, improving code organization and readability.
- Enhanced status management logic to provide clearer feedback on transaction progress, including error handling and waiting states.
- Updated the rendering logic for status icons and colors to align with the new status management approach.

This update significantly improves the clarity and maintainability of the TransactionLifecycleDialog, enhancing user feedback during token transfers in the Simple Bridge application.
… readability

- Adjusted the formatting of the transaction status retrieval logic in the TransactionLifecycleDialog component for enhanced clarity.
- Ensured consistent code style by adding a newline at the end of the file.

This update improves the maintainability of the TransactionLifecycleDialog, contributing to a cleaner codebase in the Torus Bridge application.
…d error handling and configuration management

- Introduced constants for gas, polling, and confirmation configurations to streamline the code and improve maintainability.
- Refactored user rejection error handling into a dedicated function for better clarity and reusability.
- Simplified chain switching logic and error handling, ensuring consistent behavior across different transaction states.
- Improved polling mechanism for balance confirmation with configurable parameters, enhancing user feedback during transfers.

This update enhances the clarity and maintainability of the use-simple-bridge-orchestrated-transfer hook in the Torus Bridge application, providing a more robust user experience during token transfers.
@rodrigooler rodrigooler marked this pull request as ready for review September 29, 2025 20:52
@rodrigooler rodrigooler requested a review from a team as a code owner September 29, 2025 20:52
@Copilot Copilot AI review requested due to automatic review settings September 29, 2025 20:52
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements a simplified bridge interface for easier token transfers between Base and Native Torus chains, consolidating the two-step process into a single seamless flow. It introduces new UI components, improves asset organization, and updates ESLint configuration.

  • Adds a Simple Bridge page with orchestrated two-step transfers (Base ↔ Native Torus)
  • Introduces comprehensive transaction lifecycle tracking with progress dialogs and retry mechanisms
  • Reorganizes asset structure and updates icon paths throughout the application

Reviewed Changes

Copilot reviewed 17 out of 30 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tooling/eslint/react.js Disables preserve-manual-memoization rule
apps/torus-bridge/src/consts/chains.ts Updates logo path to new asset structure
apps/torus-bridge/src/app/simple/page.tsx Creates Simple Bridge page with metadata
apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-orchestrated-transfer.ts Implements orchestrated transfer logic with comprehensive error handling
apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-dual-wallet.ts Manages dual wallet connections for bridge operations
apps/torus-bridge/src/app/simple/_components/ Various Simple Bridge UI components
apps/torus-bridge/src/app/_components/ Updates existing components with new asset paths and improved functionality
Comments suppressed due to low confidence (2)

apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-orchestrated-transfer.ts:1

  • This onClick handler contains only a placeholder comment and no actual implementation. Either implement the functionality to open explorer links or remove the non-functional button.
"use client";

apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx:1

  • The hardcoded zero address should be defined as a named constant to improve maintainability and make the intent clearer.
"use client";

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

- Added detailed JSDoc comments for the `executeBaseToNativeStep1` function, clarifying its parameters, return type, and potential errors.
- Introduced a new `getExplorerUrl` function with comprehensive documentation for generating transaction explorer URLs based on chain name and transaction hash.
- This update improves code clarity and maintainability in the Torus Bridge application by providing thorough documentation for the Base-to-Native bridge flow functions.
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

♻️ Duplicate comments (1)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (1)

23-53: Add JSDoc documentation for the interface.

The BaseToNativeStep1Params interface lacks JSDoc documentation, violating the coding guidelines requirement to document all code with JSDoc conventions. While past reviews flagged this issue as addressed, the documentation is still missing.

As per coding guidelines

Add JSDoc to document the interface and each field:

+/**
+ * Parameters for executing Step 1 of the Base-to-Native bridge flow.
+ * Orchestrates the Base → Torus EVM transfer including chain switching,
+ * transaction signing, and balance-based confirmation.
+ */
 interface BaseToNativeStep1Params {
+  /** Amount of TORUS tokens to transfer as a string */
   amount: string;
+  /** Target EVM address in hex format (0x...) for transfer recipient */
   evmAddress: string;
+  /** Current blockchain configuration */
   chain: Chain;
+  /** Function to switch wallet to target chain, returns the new chain ID */
   switchChain: (params: { chainId: number }) => Promise<{ id: number }>;
+  /** Function to initiate Hyperlane cross-chain transfer */
   triggerHyperlaneTransfer: (params: {
     origin: string;
     destination: string;
     tokenIndex: number;
     amount: string;
     recipient: string;
   }) => Promise<unknown>;
+  /** Warp Core configuration for token connections and routing */
   warpCore: {
     tokens: {
       chainName: string;
       symbol: string;
       getConnectionForChain: (chain: string) => unknown;
     }[];
   };
+  /** Refetch Torus EVM balance, returns status and balance data */
   refetchTorusEvmBalance: () => Promise<{
     status: string;
     data?: { value: bigint };
   }>;
+  /** Optional current Torus EVM balance with value as bigint */
   torusEvmBalance?: { value: bigint };
+  /** Update bridge UI state */
   updateBridgeState: (updates: {
     step: SimpleBridgeStep;
     errorMessage?: string;
   }) => void;
+  /** Add transaction entries to UI */
   addTransaction: (tx: SimpleBridgeTransaction) => void;
+  /** Generate blockchain explorer URLs for transaction hashes */
   getExplorerUrl: (txHash: string, chainName: string) => string;
 }
🧹 Nitpick comments (1)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (1)

362-661: Consider breaking down the function into smaller units.

The function is 187 lines and handles multiple responsibilities: chain switching, transaction signing, receipt confirmation, and balance polling. While the implementation is correct, breaking it into smaller, focused helper functions would improve maintainability and testability.

Consider extracting:

  • Chain switching logic with retry (lines 401-514) into a helper function
  • Transaction signing and receipt confirmation (lines 516-581) into a separate function
  • Balance polling logic (lines 584-649) into a reusable helper

This refactor is optional and can be deferred, as the current sequential flow is clear for this specific use case.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between afe4109 and d8f779b.

📒 Files selected for processing (2)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
🧠 Learnings (2)
📚 Learning: 2025-07-27T01:55:40.478Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: packages/torus-sdk-ts/src/chain/CLAUDE.md:0-0
Timestamp: 2025-07-27T01:55:40.478Z
Learning: Applies to packages/torus-sdk-ts/src/chain/**/*.{ts,tsx} : Document all exports with JSDoc comments.

Applied to files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
📚 Learning: 2025-07-27T01:55:40.478Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: packages/torus-sdk-ts/src/chain/CLAUDE.md:0-0
Timestamp: 2025-07-27T01:55:40.478Z
Learning: Applies to packages/torus-sdk-ts/src/chain/**/*.{ts,tsx} : Mark temporary uses of `z.unknown()` in schemas with `// TODO: fix z.unknown() holes` comments.

Applied to files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
🧬 Code graph analysis (1)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (4)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (1)
  • SimpleBridgeTransaction (59-68)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts (8)
  • BASE_CHAIN_ID (1-1)
  • POLLING_CONFIG (11-16)
  • isUserRejectionError (51-68)
  • UserRejectedError (26-31)
  • withTimeout (38-49)
  • TIMEOUT_CONFIG (33-36)
  • getExplorerUrl (77-94)
  • CONFIRMATION_CONFIG (18-20)
packages/torus-sdk-ts/src/types/address.ts (1)
  • SS58Address (37-37)
packages/torus-sdk-ts/src/evm.ts (1)
  • withdrawFromTorusEvm (66-131)
⏰ 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). (9)
  • GitHub Check: torus-prophet-finder-pr-activate / clean-stale
  • GitHub Check: torus-wallet-pr-activate / clean-stale
  • GitHub Check: torus-page-pr-activate / clean-stale
  • GitHub Check: torus-portal-pr-activate / clean-stale
  • GitHub Check: torus-governance-pr-activate / clean-stale
  • GitHub Check: torus-bridge-pr-activate / clean-stale
  • GitHub Check: torus-cache-pr-activate / clean-stale
  • GitHub Check: torus-allocator-pr-activate / clean-stale
  • GitHub Check: lint

…ecycle

- Updated the `TransactionLifecycleDialog` component to use the logger for debugging instead of console.log, improving logging practices.
- Refactored the `executeBaseToNativeStep1` function to simplify the return type of the `refetchTorusEvmBalance` call, enhancing code clarity.
- Added comprehensive JSDoc comments for various constants and functions in the bridge helpers, improving documentation and maintainability.
- This update enhances the overall robustness and clarity of the Torus Bridge application by improving logging practices and providing detailed documentation for key components.
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

♻️ Duplicate comments (2)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (2)

80-99: Consider refactoring the effect to avoid disabling the lint rule.

While the current implementation with the disabled lint rule works, you could eliminate the lint suppression by moving all state updates to cleanup functions, as suggested in the previous review. This follows React best practices more closely.

Apply this refactor to avoid the eslint-disable comment:

  useEffect(() => {
-   let timer: NodeJS.Timeout | undefined;
-
-   if (isCurrentlySigning) {
-     // Start timer to show warning after 30 seconds
-     timer = setTimeout(() => {
-       setShowSignatureWarning(true);
-     }, 30000);
-   } else {
-     // Reset warning immediately when not signing
-     // eslint-disable-next-line react-hooks/set-state-in-effect
-     setShowSignatureWarning(false);
-   }
-
-   return () => {
-     if (timer !== undefined) {
-       clearTimeout(timer);
-     }
-   };
+   if (!isCurrentlySigning) {
+     return () => {
+       setShowSignatureWarning(false);
+     };
+   }
+
+   const timeoutId = setTimeout(() => {
+     setShowSignatureWarning(true);
+   }, 30_000);
+
+   return () => {
+     clearTimeout(timeoutId);
+     setShowSignatureWarning(false);
+   };
  }, [isCurrentlySigning]);

201-233: Error status logic still assumes all errors occur during signing.

As noted in the previous review, this function assumes that when a transaction status is "ERROR", the error happened during the signing phase. However, SimpleBridgeTransaction.status can transition to "ERROR" during any phase ("STARTING", "SIGNING", or "CONFIRMING").

For example, if step 2 fails during confirmation (e.g., balance polling timeout in executeNativeToBaseStep2), lines 216-217 will still mark step2-sign as "error" and leave step2-confirm as "pending", even though signing succeeded and confirmation is what actually failed.

To fix this properly, track which phase the error occurred in. Consider adding an errorPhase field to SimpleBridgeTransaction:

// In simple-bridge-types.ts
export interface SimpleBridgeTransaction {
  // ... existing fields
  errorPhase?: "STARTING" | "SIGNING" | "CONFIRMING"; // Track where error occurred
}

Then update this function to use that information:

  function getErrorStepStatus(stepId: string): StepStatus | null {
    const isStep1 = stepId.startsWith("step1");
    const isStep2 = stepId.startsWith("step2");
    const step2Transaction = transactions.find((tx) => tx.step === 2);
    const step1Transaction = transactions.find((tx) => tx.step === 1);

    // Check if step 2 has an error
    if (step2Transaction?.status === "ERROR") {
      // All step 1 items should be green when step 2 fails
      if (isStep1) {
        return "completed";
      }

      // Step 2 handling
      if (isStep2) {
-       if (stepId === "step2-sign") return "error";
-       if (stepId === "step2-confirm") return "pending";
+       const errorPhase = step2Transaction.errorPhase || "SIGNING";
+       if (errorPhase === "CONFIRMING") {
+         if (stepId === "step2-sign") return "completed";
+         if (stepId === "step2-confirm") return "error";
+       } else {
+         if (stepId === "step2-sign") return "error";
+         if (stepId === "step2-confirm") return "pending";
+       }
      }
    }

    // Check if step 1 has an error
    if (step1Transaction?.status === "ERROR" && isStep1) {
-     if (stepId === "step1-sign") return "error";
-     if (stepId === "step1-confirm") return "pending";
+     const errorPhase = step1Transaction.errorPhase || "SIGNING";
+     if (errorPhase === "CONFIRMING") {
+       if (stepId === "step1-sign") return "completed";
+       if (stepId === "step1-confirm") return "error";
+     } else {
+       if (stepId === "step1-sign") return "error";
+       if (stepId === "step1-confirm") return "pending";
+     }
    }

    // Step 1 error means step 2 is all pending
    if (step1Transaction?.status === "ERROR" && isStep2) {
      return "pending";
    }

    return null;
  }

Remember to set errorPhase when recording errors in your flow functions (e.g., executeNativeToBaseStep2).

🧹 Nitpick comments (1)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (1)

359-658: Consider breaking down the function for maintainability.

executeBaseToNativeStep2 is 299 lines and orchestrates multiple concerns: chain switching with retries, transaction signing, receipt waiting, and balance polling. While the current implementation is functional, extracting these phases into separate helper functions would improve testability and readability.

Consider refactoring into focused helpers:

async function switchToTorusEvmWithRetry(params: {
  walletClient: WalletClient;
  torusEvmChainId: number;
  switchChain: (params: { chainId: number }) => Promise<{ id: number }>;
  addTransaction: (tx: SimpleBridgeTransaction) => void;
  updateBridgeState: (updates: { step: SimpleBridgeStep; errorMessage?: string }) => void;
}): Promise<void> {
  // Lines 408-511: Chain switch retry logic
}

async function executeWithdrawalTransaction(params: {
  walletClient: WalletClient;
  torusEvmChain: Chain;
  selectedAccount: { address: SS58Address };
  amountRems: bigint;
  refetchTorusEvmBalance: () => Promise<unknown>;
  addTransaction: (tx: SimpleBridgeTransaction) => void;
  updateBridgeState: (updates: { step: SimpleBridgeStep; errorMessage?: string }) => void;
}): Promise<string> {
  // Lines 513-563: Withdrawal signing and error handling
}

async function waitForNativeBalanceIncrease(params: {
  amount: string;
  nativeBalance?: { value: bigint };
  refetchNativeBalance: () => Promise<unknown>;
  txHash: string;
  addTransaction: (tx: SimpleBridgeTransaction) => void;
  updateBridgeState: (updates: { step: SimpleBridgeStep; errorMessage?: string }) => void;
  getExplorerUrl: (txHash: string, chainName: string) => string;
}): Promise<void> {
  // Lines 580-646: Balance polling and confirmation
}

export async function executeBaseToNativeStep2(params: BaseToNativeStep2Params) {
  // Orchestrate the helpers
  await switchToTorusEvmWithRetry(...);
  const txHash = await executeWithdrawalTransaction(...);
  await waitForNativeBalanceIncrease(...);
  updateBridgeState({ step: SimpleBridgeStep.COMPLETE });
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d8f779b and 651d19e.

📒 Files selected for processing (3)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx
🧠 Learnings (2)
📚 Learning: 2025-07-27T01:55:40.478Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: packages/torus-sdk-ts/src/chain/CLAUDE.md:0-0
Timestamp: 2025-07-27T01:55:40.478Z
Learning: Applies to packages/torus-sdk-ts/src/chain/**/*.{ts,tsx} : Document all exports with JSDoc comments.

Applied to files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
📚 Learning: 2025-07-27T01:55:40.478Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: packages/torus-sdk-ts/src/chain/CLAUDE.md:0-0
Timestamp: 2025-07-27T01:55:40.478Z
Learning: Applies to packages/torus-sdk-ts/src/chain/**/*.{ts,tsx} : Mark temporary uses of `z.unknown()` in schemas with `// TODO: fix z.unknown() holes` comments.

Applied to files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
🧬 Code graph analysis (2)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (5)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (1)
  • SimpleBridgeTransaction (59-68)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts (9)
  • BASE_CHAIN_ID (5-5)
  • POLLING_CONFIG (30-35)
  • formatErrorForUser (149-241)
  • isUserRejectionError (100-117)
  • UserRejectedError (53-58)
  • withTimeout (82-93)
  • TIMEOUT_CONFIG (66-69)
  • getExplorerUrl (126-143)
  • CONFIRMATION_CONFIG (42-44)
packages/torus-sdk-ts/src/types/address.ts (1)
  • SS58Address (37-37)
packages/torus-sdk-ts/src/evm.ts (1)
  • withdrawFromTorusEvm (66-131)
packages/torus-utils/src/try-catch.ts (1)
  • tryAsync (97-101)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (2)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (2)
  • SimpleBridgeDirection (3-3)
  • SimpleBridgeTransaction (59-68)
apps/torus-bridge/src/utils/logger.ts (1)
  • logger (1-8)
⏰ 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). (5)
  • GitHub Check: torus-bridge-pr-activate / clean-stale
  • GitHub Check: torus-wallet-pr-activate / clean-stale
  • GitHub Check: torus-governance-pr-activate / clean-stale
  • GitHub Check: torus-portal-pr-activate / clean-stale
  • GitHub Check: torus-allocator-pr-activate / clean-stale
🔇 Additional comments (6)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (6)

1-58: LGTM on imports and type definitions.

The imports are properly organized and all used. The TransactionLifecycleDialogProps and LifecycleStep interfaces are well-structured and provide clear contracts for the component.


101-199: LGTM on step creation functions.

The createBaseToNativeSteps and createNativeToBaseSteps functions are well-structured, clearly defining the steps for each direction with appropriate metadata (titles, descriptions, estimated times, signature requirements).


235-322: LGTM on status calculation logic.

The getStep1Status, getStep2Status, and getStepStatus functions correctly implement the state machine for transaction lifecycle steps, properly transitioning through pending → active → completed states based on currentStep.


324-389: LGTM on helper functions.

The getStatusIcon, getStatusColor, and getCurrentMessage functions provide clean abstractions with appropriate visual feedback and clear user messaging throughout the transfer lifecycle.


397-416: LGTM on debug logging.

The debug logging is now properly gated behind a NODE_ENV check (line 398) and uses the logger utility (line 402) instead of direct console calls. This ensures debug output never reaches production while providing useful diagnostic information during development.


418-610: LGTM on render logic and UX considerations.

The render implementation provides excellent UX:

  • Prevents accidental dismissal during errors (lines 436-447)
  • Clear timeline visualization with status icons and colors
  • Helpful signature warning after 30-second delay (lines 512-524)
  • Expandable transaction details via accordion (lines 534-585)
  • Appropriate retry and close actions (lines 592-606)

The component structure is clean and maintainable.

…ion lifecycle

- Updated the `TransactionLifecycleDialog` component to check for `errorPhase` during step 2 handling, improving error state management.
- Added `errorPhase` property to the `SimpleBridgeTransaction` interface to indicate where an error occurred in the transaction process.
- Enhanced JSDoc comments for the `BaseToNativeStep1Params` interface, clarifying the purpose of each parameter involved in the transfer process.
- This update improves the robustness and clarity of the Torus Bridge application by refining error handling and providing detailed documentation for key components.
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 (3)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (1)

73-99: Refactor the effect to avoid setState in the render cycle.

The eslint-disable comment on line 90 suppresses the react-hooks/set-state-in-effect warning but doesn't resolve the underlying issue. Calling setShowSignatureWarning(false) synchronously during render can cause cascading updates and unpredictable behavior.

Refactor the effect to schedule state changes via cleanup only:

  // Manage signature warning timer
  useEffect(() => {
-   let timer: NodeJS.Timeout | undefined;
-
-   if (isCurrentlySigning) {
-     // Start timer to show warning after 30 seconds
-     timer = setTimeout(() => {
-       setShowSignatureWarning(true);
-     }, 30000);
-   } else {
-     // Reset warning immediately when not signing
-     // eslint-disable-next-line react-hooks/set-state-in-effect
-     setShowSignatureWarning(false);
-   }
-
-   return () => {
-     if (timer !== undefined) {
-       clearTimeout(timer);
-     }
-   };
+   if (!isCurrentlySigning) {
+     return () => {
+       setShowSignatureWarning(false);
+     };
+   }
+
+   const timerId = setTimeout(() => {
+     setShowSignatureWarning(true);
+   }, 30_000);
+
+   return () => {
+     clearTimeout(timerId);
+     setShowSignatureWarning(false);
+   };
  }, [isCurrentlySigning]);

This moves all state updates to the cleanup phase, eliminating the lint warning and improving predictability.

apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts (2)

660-663: Remove unsafe type assertion and fix interface typing.

The type assertion bypasses type safety. The NativeToBaseStep2Params interface (line 356) declares refetchBaseBalance as Promise<unknown>, forcing this unsafe cast. Update the interface to properly type the return value, then remove the assertion.

Update the interface (line 356):

-  refetchBaseBalance: () => Promise<unknown>;
+  refetchBaseBalance: () => Promise<{
+    status: string;
+    data?: { value: bigint };
+  }>;

Then remove the cast:

-        const baseRefetchResult = (await refetchBaseBalance()) as {
-          status: string;
-          data?: { value: bigint };
-        };
+        const baseRefetchResult = await refetchBaseBalance();

651-653: CRITICAL: Fix precision loss and use fresh baseline.

Two issues here:

  1. Precision loss: parseFloat(amount) loses decimal precision on token amounts. This was flagged in a previous review (marked addressed in commit fe5095e) but parseFloat remains. Pass the original string to toNano.

  2. Stale baseline: You refetch but then read the old baseBalance param instead of using the refetch result.

Apply this diff:

-  await refetchBaseBalance();
-  const baseBaselineBalance = baseBalance?.value || 0n;
-  const baseExpectedIncrease = toNano(parseFloat(amount));
+  const baseRefreshResult = await refetchBaseBalance();
+  const baseBaselineBalance = baseRefreshResult.data?.value || 0n;
+  const baseExpectedIncrease = toNano(amount.trim());
🧹 Nitpick comments (1)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts (1)

193-196: Remove redundant type assertion.

The type assertion is unnecessary because refetchTorusEvmBalance is already declared in NativeToBaseStep1Params (lines 46-49) to return Promise<{ status: string; data?: { value: bigint } }>. The cast bypasses TypeScript's type checking without adding value.

Apply this diff:

-            const refetchResult = (await refetchTorusEvmBalance()) as {
-              status: string;
-              data?: { value: bigint };
-            };
+            const refetchResult = await refetchTorusEvmBalance();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 651d19e and 95175f4.

📒 Files selected for processing (4)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (1 hunks)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
🧠 Learnings (2)
📚 Learning: 2025-09-05T12:17:21.863Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-05T12:17:21.863Z
Learning: Applies to **/*.{ts,tsx} : Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation

Applied to files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
📚 Learning: 2025-07-27T01:55:40.478Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: packages/torus-sdk-ts/src/chain/CLAUDE.md:0-0
Timestamp: 2025-07-27T01:55:40.478Z
Learning: Applies to packages/torus-sdk-ts/src/chain/**/*.{ts,tsx} : Document all exports with JSDoc comments.

Applied to files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
🧬 Code graph analysis (4)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (1)
packages/torus-sdk-ts/src/types/address.ts (1)
  • SS58Address (37-37)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts (5)
packages/torus-sdk-ts/src/types/address.ts (1)
  • SS58Address (37-37)
packages/torus-sdk-ts/src/chain/balances.ts (1)
  • transferAllowDeath (78-84)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (1)
  • SimpleBridgeTransaction (59-69)
packages/torus-sdk-ts/src/evm.ts (1)
  • convertH160ToSS58 (22-42)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts (7)
  • isUserRejectionError (100-117)
  • formatErrorForUser (149-241)
  • UserRejectedError (53-58)
  • POLLING_CONFIG (30-35)
  • withTimeout (82-93)
  • TIMEOUT_CONFIG (66-69)
  • getExplorerUrl (126-143)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (2)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (2)
  • SimpleBridgeDirection (3-3)
  • SimpleBridgeTransaction (59-69)
apps/torus-bridge/src/utils/logger.ts (1)
  • logger (1-8)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (5)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (1)
  • SimpleBridgeTransaction (59-69)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts (9)
  • BASE_CHAIN_ID (5-5)
  • POLLING_CONFIG (30-35)
  • formatErrorForUser (149-241)
  • isUserRejectionError (100-117)
  • UserRejectedError (53-58)
  • withTimeout (82-93)
  • TIMEOUT_CONFIG (66-69)
  • getExplorerUrl (126-143)
  • CONFIRMATION_CONFIG (42-44)
packages/torus-sdk-ts/src/types/address.ts (1)
  • SS58Address (37-37)
packages/torus-sdk-ts/src/evm.ts (1)
  • withdrawFromTorusEvm (66-131)
packages/torus-utils/src/try-catch.ts (1)
  • tryAsync (97-101)
⏰ 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). (9)
  • GitHub Check: torus-bridge-pr-activate / clean-stale
  • GitHub Check: torus-prophet-finder-pr-activate / clean-stale
  • GitHub Check: torus-allocator-pr-activate / clean-stale
  • GitHub Check: torus-wallet-pr-activate / clean-stale
  • GitHub Check: torus-page-pr-activate / clean-stale
  • GitHub Check: torus-cache-pr-activate / clean-stale
  • GitHub Check: torus-governance-pr-activate / docker-build
  • GitHub Check: torus-portal-pr-activate / clean-stale
  • GitHub Check: torus-governance-pr-activate / clean-stale
🔇 Additional comments (3)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (1)

1-69: LGTM! Well-structured type definitions.

The types comprehensively cover the bridge UI requirements:

  • Clear direction and step enums
  • Proper wallet state separation (Substrate vs EVM)
  • Transaction metadata including errorPhase for precise error mapping

The errorPhase field on SimpleBridgeTransaction (lines 68) is particularly important for the dialog component to correctly identify which phase failed.

apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (1)

101-618: Well-architected multi-step UI with comprehensive state handling.

The component effectively manages:

  • Dual direction flows (base-to-native / native-to-base)
  • Step-by-step progress visualization with icons and connectors
  • Conditional rendering for signatures, errors, and transaction details
  • Proper dialog lifecycle (escape/outside-click prevention during errors)
  • Development-gated debug logging (lines 406-425)

The step creation functions (lines 101-181), status helpers (lines 244-331), and render logic (lines 441-618) are clear and maintainable.

apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (1)

1-697: LGTM! File follows best practices.

This file demonstrates excellent implementation:

  1. ✅ Comprehensive JSDoc on all interfaces and exported functions
  2. ✅ Proper interval cleanup with intervalId tracking and isPollingActive flags (lines 252-253, 623)
  3. ✅ No precision loss: uses toNano(amount.trim()) throughout (lines 249, 411, 616)
  4. ✅ Properly typed refetch functions (lines 354-363)
  5. ✅ Chain verification after switch (lines 465-470)
  6. ✅ Uses correct chain object from config after switch (lines 549-560)
  7. ✅ Resource cleanup in all error paths (lines 290-293, 664-666)

The patterns here should be applied to the native-to-base flow file as well.

Comment on lines 230 to 234
// Check if step 1 has an error
if (step1Transaction?.status === "ERROR" && isStep1) {
if (stepId === "step1-sign") return "error";
if (stepId === "step1-confirm") return "pending";
}
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

Make step 1 error mapping consistent with step 2.

Step 2 error handling (lines 214-227) correctly checks errorPhase to determine whether the error occurred during signing or confirmation. However, step 1 handling still assumes all errors happen during signing:

if (step1Transaction?.status === "ERROR" && isStep1) {
  if (stepId === "step1-sign") return "error";
  if (stepId === "step1-confirm") return "pending";
}

This creates an inconsistency—if a step 1 confirmation error occurs (e.g., network timeout), the dialog will incorrectly highlight the signing phase.

Apply this diff to align step 1 with step 2's errorPhase logic:

  // Check if step 1 has an error
  if (step1Transaction?.status === "ERROR" && isStep1) {
-   if (stepId === "step1-sign") return "error";
-   if (stepId === "step1-confirm") return "pending";
+   const errorPhase = step1Transaction.errorPhase;
+   if (errorPhase === "sign" && stepId === "step1-sign") return "error";
+   if (errorPhase === "confirm" && stepId === "step1-confirm") return "error";
+   // Fallback if errorPhase not set
+   if (!errorPhase) {
+     if (stepId === "step1-sign") return "error";
+     if (stepId === "step1-confirm") return "pending";
+   }
+   return "pending";
  }

Ensure that when you record a step 1 error (in the orchestration hooks), you also set errorPhase to either "sign" or "confirm".

🤖 Prompt for AI Agents
In
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx
around lines 230-234, the step 1 error mapping wrongly assumes all errors are in
the signing phase; update the logic to mirror step 2 by checking
step1Transaction?.errorPhase and map "sign" -> mark "step1-sign" as "error" and
"step1-confirm" as "pending", and map "confirm" -> mark "step1-confirm" as
"error" and "step1-sign" as "success"/not error (consistent with step 2
behavior); also ensure orchestration that records step1 errors sets
step1Transaction.errorPhase to either "sign" or "confirm".

…flow

- Enhanced the `TransactionLifecycleDialog` component to better manage error states by checking the `errorPhase` during transaction lifecycle steps.
- Updated the polling mechanism in the `executeNativeToBaseStep2` function to include a cancellation flag, ensuring that polling stops when no longer needed.
- Improved error handling by clearing the polling interval upon errors or successful balance confirmation, preventing unnecessary operations.
- This update enhances the robustness and clarity of the Torus Bridge application by refining error management and optimizing polling behavior.
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: 4

♻️ Duplicate comments (3)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts (2)

193-196: Remove unsafe type assertion.

The type assertion on refetchTorusEvmBalance() bypasses type safety. The return type is already properly defined in the NativeToBaseStep1Params interface (lines 46-49), making the cast unnecessary and unsafe.

As per coding guidelines.

Apply this diff:

-            const refetchResult = (await refetchTorusEvmBalance()) as {
-              status: string;
-              data?: { value: bigint };
-            };
+            const refetchResult = await refetchTorusEvmBalance();

667-670: Remove unsafe type assertion and tighten interface typing.

The type assertion bypasses type safety. Instead of using runtime casts, update the refetchBaseBalance return type in the NativeToBaseStep2Params interface (line 356).

As per coding guidelines.

Update the interface:

   /** Function to refetch Base balance */
-  refetchBaseBalance: () => Promise<unknown>;
+  refetchBaseBalance: () => Promise<{ status: string; data?: { value: bigint } }>;

Then remove the cast:

-        const baseRefetchResult = (await refetchBaseBalance()) as {
-          status: string;
-          data?: { value: bigint };
-        };
+        const baseRefetchResult = await refetchBaseBalance();
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (1)

80-99: Refactor the signature warning effect to eliminate the eslint-disable directive.

The eslint-disable comment on line 90 bypasses the react-hooks/set-state-in-effect rule rather than addressing the underlying issue. Calling setState directly in the effect body (line 91) can trigger cascading renders. Refactor the effect so state updates only occur in the timeout callback or cleanup function.

Apply this diff to properly structure the effect:

  // Manage signature warning timer
  useEffect(() => {
-   let timer: NodeJS.Timeout | undefined;
-
    if (isCurrentlySigning) {
-     // Start timer to show warning after 30 seconds
-     timer = setTimeout(() => {
+     const timer = setTimeout(() => {
        setShowSignatureWarning(true);
      }, 30000);
-   } else {
-     // Reset warning immediately when not signing
-     // eslint-disable-next-line react-hooks/set-state-in-effect
-     setShowSignatureWarning(false);
-   }

-   return () => {
-     if (timer !== undefined) {
-       clearTimeout(timer);
-     }
-   };
+     return () => {
+       clearTimeout(timer);
+       setShowSignatureWarning(false);
+     };
+   }
+
+   // When not signing, reset the warning in cleanup
+   return () => {
+     setShowSignatureWarning(false);
+   };
  }, [isCurrentlySigning]);
🧹 Nitpick comments (2)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (2)

101-181: Reduce duplication between step creation functions.

createBaseToNativeSteps and createNativeToBaseSteps contain nearly identical logic with only text differences. This duplication makes maintenance harder and increases the risk of inconsistencies.

Consider extracting a shared helper:

const createSteps = (
  direction: SimpleBridgeDirection,
  step1Tx: SimpleBridgeTransaction | undefined,
  step2Tx: SimpleBridgeTransaction | undefined
): LifecycleStep[] => {
  const isBaseToNative = direction === "base-to-native";
  const step1Chain = isBaseToNative ? "Base" : "Native";
  const step2Chain = isBaseToNative ? "Withdrawal" : "Base";
  const step1Network = isBaseToNative ? "Base" : "Native Torus";
  const step2Network = isBaseToNative ? "Native Torus" : "Base";
  const step1Time = isBaseToNative ? "~1-2 minutes" : "~30-60 seconds";
  const step2Time = isBaseToNative ? "~30-60 seconds" : "~1-2 minutes";

  return [
    {
      id: "step1-sign",
      title: `Sign ${step1Chain} Transaction`,
      description: `Please sign the transaction in your ${step1Network} wallet`,
      icon: <Wallet className="h-4 w-4" />,
      status: getStepStatus("step1-sign"),
      isSignatureRequired: true,
      errorDetails: step1Tx?.errorDetails,
    },
    {
      id: "step1-confirm",
      title: `${step1Chain} Transaction Confirming`,
      description: `Waiting for ${step1Network} confirmation`,
      icon: <Clock className="h-4 w-4" />,
      status: getStepStatus("step1-confirm"),
      estimatedTime: step1Time,
      txHash: step1Tx?.txHash,
      explorerUrl: step1Tx?.explorerUrl,
    },
    // ... similar pattern for step2
  ];
};

Then use it in getLifecycleSteps:

const getLifecycleSteps = (): LifecycleStep[] => {
  const steps = createSteps(direction, step1Transaction, step2Transaction);
  // ... rest of the function
};

35-68: Add JSDoc documentation for the component and interfaces.

Per the coding guidelines for **/*.{ts,tsx} files, all code should follow JSDoc conventions. The TransactionLifecycleDialog component and its props interface lack documentation.

Based on coding guidelines

Add JSDoc comments:

+/**
+ * Props for the TransactionLifecycleDialog component.
+ */
 interface TransactionLifecycleDialogProps {
+  /** Whether the dialog is open */
   isOpen: boolean;
+  /** Callback invoked when the dialog should close */
   onClose: () => void;
+  /** Direction of the bridge transfer */
   direction: SimpleBridgeDirection;
+  /** Current step in the bridge process */
   currentStep: SimpleBridgeStep;
+  /** Array of transaction objects for both steps */
   transactions: SimpleBridgeTransaction[];
+  /** Amount of tokens being bridged */
   amount: string;
+  /** Optional callback to retry failed transfers */
   onRetry?: () => void;
 }

+/**
+ * Dialog component that displays the lifecycle of a bridge transfer.
+ * Shows progress through multiple steps including signing and confirmation phases.
+ *
+ * @param props - Component props
+ * @returns Dialog showing transfer progress
+ */
 export function TransactionLifecycleDialog({
   isOpen,
   onClose,
   direction,
   currentStep,
   transactions,
   amount,
   onRetry,
 }: TransactionLifecycleDialogProps) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 95175f4 and 94941a4.

📒 Files selected for processing (2)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts
🧠 Learnings (2)
📚 Learning: 2025-09-05T12:17:21.863Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-05T12:17:21.863Z
Learning: Applies to **/*.{ts,tsx} : Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation

Applied to files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts
📚 Learning: 2025-07-27T01:55:40.478Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: packages/torus-sdk-ts/src/chain/CLAUDE.md:0-0
Timestamp: 2025-07-27T01:55:40.478Z
Learning: Applies to packages/torus-sdk-ts/src/chain/**/*.{ts,tsx} : Document all exports with JSDoc comments.

Applied to files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts
🧬 Code graph analysis (2)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (2)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (2)
  • SimpleBridgeDirection (3-3)
  • SimpleBridgeTransaction (59-69)
apps/torus-bridge/src/utils/logger.ts (1)
  • logger (1-8)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts (5)
packages/torus-sdk-ts/src/types/address.ts (1)
  • SS58Address (37-37)
packages/torus-sdk-ts/src/chain/balances.ts (1)
  • transferAllowDeath (78-84)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (1)
  • SimpleBridgeTransaction (59-69)
packages/torus-sdk-ts/src/evm.ts (1)
  • convertH160ToSS58 (22-42)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts (7)
  • isUserRejectionError (100-117)
  • formatErrorForUser (149-241)
  • UserRejectedError (53-58)
  • POLLING_CONFIG (30-35)
  • withTimeout (82-93)
  • TIMEOUT_CONFIG (66-69)
  • getExplorerUrl (126-143)
⏰ 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). (4)
  • GitHub Check: turbo-check / run
  • GitHub Check: typecheck
  • GitHub Check: check
  • GitHub Check: lint

Comment on lines 651 to 653
await refetchBaseBalance();
const baseBaselineBalance = baseBalance?.value || 0n;
const baseExpectedIncrease = toNano(parseFloat(amount));
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: Fix baseline calculation and precision loss (regressions).

This code has two critical issues that were supposedly fixed in previous commits but have regressed:

  1. Line 651-652: The refetchBaseBalance() result is discarded, and the code reads the stale baseBalance parameter instead. This was marked as addressed in commit fe5095e but is still present.

  2. Line 653: parseFloat(amount) causes precision loss on decimal token amounts. This was marked as addressed in commit afe4109 but is still present.

Note: Line 170 correctly uses toNano(amount.trim()) without parseFloat, demonstrating the proper pattern.

Apply this diff:

-  await refetchBaseBalance();
-  const baseBaselineBalance = baseBalance?.value || 0n;
-  const baseExpectedIncrease = toNano(parseFloat(amount));
+  const baseRefreshResult = await refetchBaseBalance();
+  const baseBaselineBalance = (baseRefreshResult as { data?: { value: bigint } }).data?.value || 0n;
+  const baseExpectedIncrease = toNano(amount.trim());

Or better yet, properly type refetchBaseBalance in the interface at line 356 to return the expected shape, then use:

-  await refetchBaseBalance();
-  const baseBaselineBalance = baseBalance?.value || 0n;
-  const baseExpectedIncrease = toNano(parseFloat(amount));
+  const baseRefreshResult = await refetchBaseBalance();
+  const baseBaselineBalance = baseRefreshResult.data?.value || 0n;
+  const baseExpectedIncrease = toNano(amount.trim());
📝 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
await refetchBaseBalance();
const baseBaselineBalance = baseBalance?.value || 0n;
const baseExpectedIncrease = toNano(parseFloat(amount));
// Capture the fresh balance instead of discarding it
const baseRefreshResult = await refetchBaseBalance();
// Read the actual returned value
const baseBaselineBalance = (baseRefreshResult as { data?: { value: bigint } }).data?.value || 0n;
// Pass the raw string (trimmed) to toNano to avoid precision loss
const baseExpectedIncrease = toNano(amount.trim());

- Introduced a new `TransactionStepItem` component to streamline the rendering of transaction steps in the `TransactionLifecycleDialog`, improving code organization and readability.
- Implemented a custom hook, `useTransactionLifecycleSteps`, to encapsulate the logic for managing transaction lifecycle steps, enhancing reusability and separation of concerns.
- Updated the `TransactionLifecycleDialog` to utilize the new hook and component, simplifying the main component's structure and improving maintainability.
- This update enhances the overall clarity and robustness of the Torus Bridge application by refining the transaction lifecycle management and promoting better code practices.
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 (2)
apps/torus-bridge/src/app/simple/_components/transaction-step-item.tsx (2)

154-155: Don’t infer network from title text

String-matching the title to decide the network is brittle—small copy changes will silently break it. Please pass the actual chainName (or similar metadata) through the lifecycle step and render that instead.


162-165: Remove misleading gas-fee estimate

The fixed “~0.001 ETH” note is wrong for Native Torus and can be off by orders of magnitude on Base. It misleads users about costs. Either make this network-specific with real data or drop the row for now.

-                <div>
-                  Gas Fees:{" "}
-                  <em>Estimated ~0.001 ETH (actual varies by network)</em>
-                </div>
🧹 Nitpick comments (9)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-polling.ts (1)

53-60: Extract hardcoded decimal constant.

The value 1e18 is hardcoded in multiple locations for converting TORUS balances to human-readable format. Consider extracting this to a named constant for maintainability.

Apply this diff:

+const TORUS_DECIMALS = 1e18;
+
 /**
  * Polls balance until target is reached or timeout occurs.
  *
  * Features:
  * - Polls at configured intervals (POLLING_CONFIG.INTERVAL_MS)
  * - Stops after MAX_POLLS attempts or timeout
  * - Handles refetch errors gracefully
  * - Provides detailed logging for debugging
  * - Uses immutable abort signal pattern to stop polling
  *
  * @param params - Balance polling parameters
  */
 export async function pollBalanceUntilTarget<T>(
   params: BalancePollingParams<T>,
 ): Promise<PollingResult> {
   const {
     refetchBalance,
     baselineBalance,
     expectedIncrease,
     extractValue,
     locationName,
   } = params;
 
   console.log(
-    `Polling ${locationName} balance - Baseline: ${Number(baselineBalance) / 1e18} TORUS, Expected increase: ${Number(expectedIncrease) / 1e18} TORUS`,
+    `Polling ${locationName} balance - Baseline: ${Number(baselineBalance) / TORUS_DECIMALS} TORUS, Expected increase: ${Number(expectedIncrease) / TORUS_DECIMALS} TORUS`,
   );
 
   const targetBalance = baselineBalance + expectedIncrease;
   console.log(
-    `Target balance: ${Number(targetBalance) / 1e18} TORUS`,
+    `Target balance: ${Number(targetBalance) / TORUS_DECIMALS} TORUS`,
   );
 
   // ... rest of code ...
 
         console.log(
-          `Current balance: ${Number(currentBalance) / 1e18} TORUS`,
+          `Current balance: ${Number(currentBalance) / TORUS_DECIMALS} TORUS`,
         );

Also applies to: 93-95

apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts (3)

73-87: Stop retrying after user rejection (avoid repeated wallet prompts).

If the user rejects the switch on the first attempt, we should stop immediately instead of retrying and spamming prompts.

     if (switchError !== undefined) {
-      lastError = switchError;
-      console.log(`Switch attempt ${attempts} failed:`, switchError.message);
+      lastError = switchError;
+      console.log(`Switch attempt ${attempts} failed:`, switchError.message);
+
+      // Do not retry on user rejection
+      if (isUserRejectionError(switchError)) {
+        break;
+      }
 
       // Check if max attempts reached
       if (attempts >= POLLING_CONFIG.MAX_SWITCH_ATTEMPTS) {
         break;
       }

12-21: Consider exporting types used by exported functions.

switchChainWithRetry and throwOnChainSwitchFailure are exported and use ChainSwitchResult/ChainSwitchParams. Export these interfaces to avoid “cannot be named” issues in declaration outputs and for reuse.

-interface ChainSwitchResult {
+export interface ChainSwitchResult {
   success: boolean;
   errorMessage?: string;
   errorDetails?: string;
   isUserRejected: boolean;
 }
 
-interface ChainSwitchParams {
+export interface ChainSwitchParams {
   targetChainId: number;
   switchChain: (params: { chainId: number }) => Promise<{ id: number }>;
   getCurrentChainId: () => Promise<number>;
   chainName: string;
 }

Also applies to: 26-35


65-68: Prefer a project logger over console. (optional).*

Console logs in app code can be noisy; consider a scoped logger with levels or guard logs by env.

Also applies to: 91-103, 106-115, 124-140

apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (5)

231-245: Add errorPhase metadata for clearer UI (confirm phase).

On confirmation failure, include errorPhase: "confirm" to leverage SimpleBridgeTransaction typing and improve UX.

   if (!pollingResult.success) {
     addTransaction({
       step: 1,
       status: "ERROR",
       chainName: "Base",
       message: pollingResult.errorMessage ?? "Transfer confirmation failed",
+      errorPhase: "confirm",
       txHash: undefined,
       explorerUrl: undefined,
     });

196-205: Add errorPhase metadata for clearer UI (sign phase).

Include errorPhase: "sign" when the Hyperlane transfer is rejected/fails.

     addTransaction({
       step: 1,
       status: "ERROR",
       chainName: "Base",
       message: errorMessage,
       errorDetails,
+      errorPhase: "sign",
       txHash: undefined,
       explorerUrl: undefined,
     });

481-490: Add errorPhase metadata for clearer UI (confirm phase).

Tag the polling failure with errorPhase: "confirm" for consistency.

     addTransaction({
       step: 2,
       status: "ERROR",
       chainName: "Torus Native",
       message: pollingResult.errorMessage ?? "Withdrawal confirmation failed",
       txHash,
-      explorerUrl: explorerUrl || undefined,
+      explorerUrl: explorerUrl || undefined,
+      errorPhase: "confirm",
     });

431-439: Add errorPhase metadata for clearer UI (sign phase).

Similarly, when withdraw fails/rejected, add errorPhase: "sign".

     addTransaction({
       step: 2,
       status: "ERROR",
       chainName: "Torus EVM",
       message: errorMessage,
       errorDetails,
+      errorPhase: "sign",
       txHash: undefined,
       explorerUrl: undefined,
     });

272-308: Unused param in Step 2 (optional cleanup).

BaseToNativeStep2Params.chain is declared but never used. Remove to reduce confusion.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 94941a4 and d41dc57.

📒 Files selected for processing (7)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (1 hunks)
  • apps/torus-bridge/src/app/simple/_components/transaction-step-item.tsx (1 hunks)
  • apps/torus-bridge/src/app/simple/_components/use-transaction-lifecycle-steps.ts (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-polling.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-bridge/src/app/simple/_components/use-transaction-lifecycle-steps.ts
  • apps/torus-bridge/src/app/simple/_components/transaction-step-item.tsx
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-polling.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-bridge/src/app/simple/_components/use-transaction-lifecycle-steps.ts
  • apps/torus-bridge/src/app/simple/_components/transaction-step-item.tsx
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-polling.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts
🧠 Learnings (2)
📚 Learning: 2025-07-27T01:55:40.478Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: packages/torus-sdk-ts/src/chain/CLAUDE.md:0-0
Timestamp: 2025-07-27T01:55:40.478Z
Learning: Applies to packages/torus-sdk-ts/src/chain/**/*.{ts,tsx} : Document all exports with JSDoc comments.

Applied to files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
📚 Learning: 2025-09-05T12:17:21.863Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-05T12:17:21.863Z
Learning: Applies to **/*.{ts,tsx} : Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation

Applied to files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
🧬 Code graph analysis (6)
apps/torus-bridge/src/app/simple/_components/use-transaction-lifecycle-steps.ts (1)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (2)
  • SimpleBridgeDirection (3-3)
  • SimpleBridgeTransaction (59-69)
apps/torus-bridge/src/app/simple/_components/transaction-step-item.tsx (1)
packages/ui/src/components/clock.tsx (1)
  • Clock (23-52)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (4)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (2)
  • SimpleBridgeDirection (3-3)
  • SimpleBridgeTransaction (59-69)
apps/torus-bridge/src/app/simple/_components/use-transaction-lifecycle-steps.ts (1)
  • useTransactionLifecycleSteps (20-257)
apps/torus-bridge/src/utils/logger.ts (1)
  • logger (1-8)
apps/torus-bridge/src/app/simple/_components/transaction-step-item.tsx (1)
  • TransactionStepItem (76-185)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-polling.ts (2)
packages/torus-utils/src/try-catch.ts (1)
  • tryAsync (97-101)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts (3)
  • POLLING_CONFIG (30-35)
  • withTimeout (82-93)
  • TIMEOUT_CONFIG (66-69)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (7)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (1)
  • SimpleBridgeTransaction (59-69)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts (6)
  • BASE_CHAIN_ID (5-5)
  • isUserRejectionError (100-117)
  • formatErrorForUser (149-241)
  • UserRejectedError (53-58)
  • getExplorerUrl (126-143)
  • CONFIRMATION_CONFIG (42-44)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts (2)
  • switchChainWithRetry (48-141)
  • throwOnChainSwitchFailure (150-160)
packages/torus-utils/src/try-catch.ts (1)
  • tryAsync (97-101)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-polling.ts (1)
  • pollEvmBalance (146-159)
packages/torus-sdk-ts/src/types/address.ts (1)
  • SS58Address (37-37)
packages/torus-sdk-ts/src/evm.ts (1)
  • withdrawFromTorusEvm (66-131)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts (2)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts (4)
  • POLLING_CONFIG (30-35)
  • isUserRejectionError (100-117)
  • formatErrorForUser (149-241)
  • UserRejectedError (53-58)
packages/torus-utils/src/try-catch.ts (1)
  • tryAsync (97-101)
⏰ 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). (4)
  • GitHub Check: turbo-check / run
  • GitHub Check: typecheck
  • GitHub Check: lint
  • GitHub Check: check
🔇 Additional comments (2)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-polling.ts (2)

146-159: LGTM!

The pollEvmBalance wrapper correctly delegates to pollBalanceUntilTarget with the appropriate extractValue function for EVM balances.


7-12: Consider using tuple pattern for consistency with coding guidelines.

The PollingResult interface uses a success boolean field, which differs from the coding guideline stating: "Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow."

While this is a local interface (not using the Result<T,E> type), consider refactoring to a tuple pattern for consistency:

-/**
- * Result of a balance polling operation.
- */
-interface PollingResult {
-  /** True if target balance was reached */
-  success: boolean;
-  /** Error message if polling failed or timed out */
-  errorMessage?: string;
-}

Then return [error, data] tuples directly from the polling functions, where error is Error | undefined and data is void | undefined.

As per coding guidelines.

Likely an incorrect or invalid review comment.

Comment on lines 34 to 64
if (step2Transaction?.status === "ERROR") {
if (isStep1) {
return "completed";
}

if (isStep2) {
const errorPhase = step2Transaction.errorPhase;
if (errorPhase === "sign" && stepId === "step2-sign") return "error";
if (errorPhase === "confirm" && stepId === "step2-confirm") return "error";

if (!errorPhase) {
if (stepId === "step2-sign") return "error";
if (stepId === "step2-confirm") return "pending";
}

return "pending";
}
}

if (step1Transaction?.status === "ERROR" && isStep1) {
const errorPhase = step1Transaction.errorPhase;
if (errorPhase === "sign" && stepId === "step1-sign") return "error";
if (errorPhase === "confirm" && stepId === "step1-confirm") return "error";

if (!errorPhase) {
if (stepId === "step1-sign") return "error";
if (stepId === "step1-confirm") return "pending";
}

return "pending";
}
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

Mark signing substeps as completed when confirmation fails

Right now, if a confirmation phase throws (e.g. step 2 confirm times out), getErrorStepStatus leaves the signing substep stuck in "pending", so the dialog tells the user they still need to sign even though that part already succeeded. That’s confusing during recovery. Please treat confirmation errors as "completed" for the corresponding sign substep so the UI reflects reality.

-        if (errorPhase === "sign" && stepId === "step2-sign") return "error";
-        if (errorPhase === "confirm" && stepId === "step2-confirm") return "error";
+        if (errorPhase === "sign" && stepId === "step2-sign") return "error";
+        if (errorPhase === "confirm") {
+          if (stepId === "step2-confirm") return "error";
+          if (stepId === "step2-sign") return "completed";
+        }
@@
-      if (errorPhase === "sign" && stepId === "step1-sign") return "error";
-      if (errorPhase === "confirm" && stepId === "step1-confirm") return "error";
+      if (errorPhase === "sign" && stepId === "step1-sign") return "error";
+      if (errorPhase === "confirm") {
+        if (stepId === "step1-confirm") return "error";
+        if (stepId === "step1-sign") return "completed";
+      }
📝 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
if (step2Transaction?.status === "ERROR") {
if (isStep1) {
return "completed";
}
if (isStep2) {
const errorPhase = step2Transaction.errorPhase;
if (errorPhase === "sign" && stepId === "step2-sign") return "error";
if (errorPhase === "confirm" && stepId === "step2-confirm") return "error";
if (!errorPhase) {
if (stepId === "step2-sign") return "error";
if (stepId === "step2-confirm") return "pending";
}
return "pending";
}
}
if (step1Transaction?.status === "ERROR" && isStep1) {
const errorPhase = step1Transaction.errorPhase;
if (errorPhase === "sign" && stepId === "step1-sign") return "error";
if (errorPhase === "confirm" && stepId === "step1-confirm") return "error";
if (!errorPhase) {
if (stepId === "step1-sign") return "error";
if (stepId === "step1-confirm") return "pending";
}
return "pending";
}
if (step2Transaction?.status === "ERROR") {
if (isStep1) {
return "completed";
}
if (isStep2) {
const errorPhase = step2Transaction.errorPhase;
if (errorPhase === "sign" && stepId === "step2-sign") return "error";
if (errorPhase === "confirm") {
if (stepId === "step2-confirm") return "error";
if (stepId === "step2-sign") return "completed";
}
if (!errorPhase) {
if (stepId === "step2-sign") return "error";
if (stepId === "step2-confirm") return "pending";
}
return "pending";
}
}
if (step1Transaction?.status === "ERROR" && isStep1) {
const errorPhase = step1Transaction.errorPhase;
if (errorPhase === "sign" && stepId === "step1-sign") return "error";
if (errorPhase === "confirm") {
if (stepId === "step1-confirm") return "error";
if (stepId === "step1-sign") return "completed";
}
if (!errorPhase) {
if (stepId === "step1-sign") return "error";
if (stepId === "step1-confirm") return "pending";
}
return "pending";
}
🤖 Prompt for AI Agents
In
apps/torus-bridge/src/app/simple/_components/use-transaction-lifecycle-steps.ts
around lines 34 to 64, update the error-handling branches so that when a
transaction errorPhase is "confirm" the corresponding "sign" substep is returned
as "completed" instead of "error" or "pending": specifically, add checks like if
(errorPhase === "confirm" && stepId === "step2-sign") return "completed" (and
the analogous case for step1: if (errorPhase === "confirm" && stepId ===
"step1-sign") return "completed"), leaving the existing behavior for sign errors
and the no-errorPhase fallback intact.

Comment on lines +147 to +152
const switchResult = await switchChainWithRetry({
targetChainId: BASE_CHAIN_ID,
switchChain,
getCurrentChainId: () => Promise.resolve(chain.id),
chainName: "Base",
});
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

Bug: Chain verification uses a stale chain ID (Step 1).

You pass getCurrentChainId: () => Promise.resolve(chain.id), which never reflects the wallet’s live chain. This can falsely “verify” a switch or fail verification, and may execute the transfer on the wrong network.

Apply this change to accept a real getter and wire it through:

 interface BaseToNativeStep1Params {
   ...
   switchChain: (params: { chainId: number }) => Promise<{ id: number }>;
+  /** Function to get the current wallet chain ID */
+  getCurrentChainId: () => Promise<number>;
   ...
 }
 
 export async function executeBaseToNativeStep1(
   params: BaseToNativeStep1Params,
 ) {
   const {
     ...
     switchChain,
+    getCurrentChainId,
     ...
   } = params;
   ...
-  const switchResult = await switchChainWithRetry({
+  const switchResult = await switchChainWithRetry({
     targetChainId: BASE_CHAIN_ID,
     switchChain,
-    getCurrentChainId: () => Promise.resolve(chain.id),
+    getCurrentChainId,
     chainName: "Base",
   });

🏁 Script executed:

#!/bin/bash
# Find all call sites to executeBaseToNativeStep1 to update them with getCurrentChainId
rg -nPU 'executeBaseToNativeStep1\s*\(' -C3 --type=ts --type=tsx

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Find all call sites to executeBaseToNativeStep1 for updating with getCurrentChainId
rg -nP 'executeBaseToNativeStep1\s*\(' -C3 --glob '*.ts' --glob '*.tsx'

Length of output: 1561


🏁 Script executed:

#!/bin/bash
# Show context around the call to executeBaseToNativeStep1 in the orchestrated transfer hook
sed -n '180,220p' apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-orchestrated-transfer.ts

Length of output: 1113


🏁 Script executed:

#!/bin/bash
# Show imports and definitions around walletClient and switchChainAsync
sed -n '1,150p' apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-orchestrated-transfer.ts

Length of output: 4460


Bug: Chain verification uses a stale chain ID (Step 1)

  • Add getCurrentChainId: () => Promise<number> to BaseToNativeStep1Params and accept it in executeBaseToNativeStep1.
  • Pass that getter into switchChainWithRetry instead of () => Promise.resolve(chain.id) in simple-bridge-base-to-native-flow.ts.
  • Update the call in use-simple-bridge-orchestrated-transfer.ts (around line 196) to include
    getCurrentChainId: () => walletClient.getChainId(),

Comment on lines +220 to +223
await refetchTorusEvmBalance();
const baselineBalance = torusEvmBalance?.value ?? 0n;
const expectedIncrease = toNano(amount.trim());

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: Baseline balance uses stale state; use refetch result.

You await refetchTorusEvmBalance() but ignore its return and read torusEvmBalance?.value (stale). This skews polling baselines.

-  await refetchTorusEvmBalance();
-  const baselineBalance = torusEvmBalance?.value ?? 0n;
-  const expectedIncrease = toNano(amount.trim());
+  const refetchResult = await refetchTorusEvmBalance();
+  const baselineBalance =
+    refetchResult.status === "error"
+      ? (torusEvmBalance?.value ?? 0n)
+      : (refetchResult.data?.value ?? 0n);
+  const expectedIncrease = toNano(amount.trim());
📝 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
await refetchTorusEvmBalance();
const baselineBalance = torusEvmBalance?.value ?? 0n;
const expectedIncrease = toNano(amount.trim());
const refetchResult = await refetchTorusEvmBalance();
const baselineBalance =
refetchResult.status === "error"
? (torusEvmBalance?.value ?? 0n)
: (refetchResult.data?.value ?? 0n);
const expectedIncrease = toNano(amount.trim());
🤖 Prompt for AI Agents
In apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
around lines 220-223, the code awaits refetchTorusEvmBalance() but then reads
torusEvmBalance?.value (stale) to compute baselineBalance; capture and use the
refetch result instead. Change the code to await and store the return value from
refetchTorusEvmBalance (e.g., const fresh = await refetchTorusEvmBalance()) and
derive baselineBalance from fresh.value (with fallback to torusEvmBalance?.value
or 0n), ensuring you preserve the bigint type when computing expectedIncrease.

Comment on lines +466 to +469
await refetchNativeBalance();
const baselineNativeBalance = nativeBalance?.value ?? 0n;
const expectedNativeIncrease = toNano(amount.trim());

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: Baseline Native balance uses stale state; use refetch result.

You await refetchNativeBalance() but ignore its return and read nativeBalance?.value (stale). Use the returned data, with fallback on error.

-  await refetchNativeBalance();
-  const baselineNativeBalance = nativeBalance?.value ?? 0n;
-  const expectedNativeIncrease = toNano(amount.trim());
+  const refetchNativeResult = await refetchNativeBalance();
+  const baselineNativeBalance =
+    refetchNativeResult.status === "error"
+      ? (nativeBalance?.value ?? 0n)
+      : (refetchNativeResult.data?.value ?? 0n);
+  const expectedNativeIncrease = toNano(amount.trim());
📝 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
await refetchNativeBalance();
const baselineNativeBalance = nativeBalance?.value ?? 0n;
const expectedNativeIncrease = toNano(amount.trim());
const refetchNativeResult = await refetchNativeBalance();
const baselineNativeBalance =
refetchNativeResult.status === "error"
? (nativeBalance?.value ?? 0n)
: (refetchNativeResult.data?.value ?? 0n);
const expectedNativeIncrease = toNano(amount.trim());
🤖 Prompt for AI Agents
In apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
around lines 466 to 469, the code awaits refetchNativeBalance() but then reads
nativeBalance?.value (which may be stale); change to use the refetch result as
the source of truth: capture the return value from await refetchNativeBalance(),
derive baselineNativeBalance from that result (with fallback to
nativeBalance?.value and then 0n), and ensure any error from the refetch is
handled so baselineNativeBalance always gets a safe numeric fallback while
keeping the existing toNano(amount.trim()) for expectedNativeIncrease.

Comment on lines +498 to +505
addTransaction({
step: 2,
status: "SUCCESS",
chainName: "Torus Native",
message: "Withdrawal complete",
txHash,
explorerUrl: getExplorerUrl(txHash, "Torus Native"),
});
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

Avoid empty explorer URLs for Torus Native.

getExplorerUrl returns "" for Torus Native. Coalesce to undefined to prevent empty links.

   addTransaction({
     step: 2,
     status: "SUCCESS",
     chainName: "Torus Native",
     message: "Withdrawal complete",
     txHash,
-    explorerUrl: getExplorerUrl(txHash, "Torus Native"),
+    explorerUrl: getExplorerUrl(txHash, "Torus Native") || undefined,
   });
📝 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
addTransaction({
step: 2,
status: "SUCCESS",
chainName: "Torus Native",
message: "Withdrawal complete",
txHash,
explorerUrl: getExplorerUrl(txHash, "Torus Native"),
});
addTransaction({
step: 2,
status: "SUCCESS",
chainName: "Torus Native",
message: "Withdrawal complete",
txHash,
explorerUrl: getExplorerUrl(txHash, "Torus Native") || undefined,
});
🤖 Prompt for AI Agents
In apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
around lines 498 to 505, the call to addTransaction uses explorerUrl:
getExplorerUrl(txHash, "Torus Native") which can return an empty string for
Torus Native; replace that with a coalesced undefined (e.g. set explorerUrl to
the function result or undefined) so empty strings are not passed through and no
empty links are created.

Comment on lines 65 to 117
const pollPromise = new Promise<PollingResult>((resolve, reject) => {
const intervalId = setInterval(() => {
void (async () => {
if (abortController.signal.aborted) {
return;
}

pollCount++;
console.log(`Poll ${pollCount}: Checking ${locationName} balance`);

const [refetchError, refetchResult] = await tryAsync(refetchBalance());

if (refetchError !== undefined) {
console.warn(
`Failed to refetch ${locationName} balance, retrying...`,
);
return;
}

if (refetchResult.status === "error") {
console.warn(`Refetch returned error status, skipping this poll`);
return;
}

const currentBalance = refetchResult.data
? extractValue(refetchResult.data)
: 0n;

console.log(
`Current balance: ${Number(currentBalance) / 1e18} TORUS`,
);

if (currentBalance >= targetBalance) {
console.log(`Balance target reached! Resolving polling`);
clearInterval(intervalId);
abortController.abort();
resolve({ success: true });
return;
}

if (pollCount >= POLLING_CONFIG.MAX_POLLS) {
console.log(`Polling timeout reached after ${pollCount} attempts`);
clearInterval(intervalId);
abortController.abort();
reject(
new Error(
`Confirmation timeout - no balance increase after ${pollCount} attempts`,
),
);
}
})();
}, POLLING_CONFIG.INTERVAL_MS);
});
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: Resource leak - interval not cleaned up on timeout.

The setInterval is only cleared within the callback (lines 99, 107) when the target is reached or MAX_POLLS is exceeded. However, when withTimeout rejects at line 119-125, the interval continues running. Although abortController.abort() at line 127 prevents further processing (line 68-70 check), the interval timer itself keeps firing until MAX_POLLS is reached, wasting resources.

Additionally, the void (async () => {})() pattern can swallow unexpected errors that occur inside the async callback, making debugging difficult.

Apply this diff to ensure proper cleanup:

   let pollCount = 0;
   const abortController = new AbortController();
+  let intervalId: NodeJS.Timeout | undefined;
 
   const pollPromise = new Promise<PollingResult>((resolve, reject) => {
-    const intervalId = setInterval(() => {
+    intervalId = setInterval(() => {
       void (async () => {
         if (abortController.signal.aborted) {
           return;
         }
 
         pollCount++;
         console.log(`Poll ${pollCount}: Checking ${locationName} balance`);
 
         const [refetchError, refetchResult] = await tryAsync(refetchBalance());
 
         if (refetchError !== undefined) {
           console.warn(
             `Failed to refetch ${locationName} balance, retrying...`,
           );
           return;
         }
 
         if (refetchResult.status === "error") {
           console.warn(`Refetch returned error status, skipping this poll`);
           return;
         }
 
         const currentBalance = refetchResult.data
           ? extractValue(refetchResult.data)
           : 0n;
 
         console.log(
           `Current balance: ${Number(currentBalance) / 1e18} TORUS`,
         );
 
         if (currentBalance >= targetBalance) {
           console.log(`Balance target reached! Resolving polling`);
-          clearInterval(intervalId);
+          if (intervalId) clearInterval(intervalId);
           abortController.abort();
           resolve({ success: true });
           return;
         }
 
         if (pollCount >= POLLING_CONFIG.MAX_POLLS) {
           console.log(`Polling timeout reached after ${pollCount} attempts`);
-          clearInterval(intervalId);
+          if (intervalId) clearInterval(intervalId);
           abortController.abort();
           reject(
             new Error(
               `Confirmation timeout - no balance increase after ${pollCount} attempts`,
             ),
           );
         }
       })();
     }, POLLING_CONFIG.INTERVAL_MS);
   });
 
   const [timeoutError, result] = await tryAsync(
     withTimeout(
       pollPromise,
       TIMEOUT_CONFIG.POLLING_OPERATION_MS,
       `${locationName} balance confirmation timeout`,
     ),
   );
 
+  // Always clear interval on exit
+  if (intervalId) {
+    clearInterval(intervalId);
+  }
   abortController.abort();
 
   if (timeoutError !== undefined) {

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

🤖 Prompt for AI Agents
In apps/torus-bridge/src/app/simple/hooks/simple-bridge-polling.ts around lines
65-117, the setInterval is not guaranteed to be cleared when withTimeout rejects
and the async IIFE swallows errors; fix by ensuring the interval is cleared in
all control flows and by using a properly awaited async function with explicit
try/catch/finally. Replace the void (async () => { ... })() pattern with a named
async handler invoked from the interval that wraps the body in try/catch and
always clears the interval on terminal conditions (target reached, MAX_POLLS
hit, or external timeout/rejection) — also ensure any caught errors are logged
and propagated to reject so we don't swallow unexpected exceptions.

- Introduced a new section in the documentation emphasizing the importance of not using `// eslint-disable` or `// eslint-disable-next-line` to suppress linting errors.
- Clarified that these directives hide underlying issues and that developers should address the root causes by refactoring code instead.
- This update aims to promote better coding practices and maintain high code quality standards within the project.
…ment

- Enhanced the `Sidebar` component in the Torus Bridge application by implementing a custom hook, `useCurrentUrl`, to track the current URL and improve active link detection.
- Updated the logic for determining active sidebar options to account for external links, ensuring accurate highlighting based on the current URL.
- Refactored the `TransactionLifecycleDialog` component to reset the signature warning state upon unmounting, improving user experience during transaction processes.
- Optimized the `useTransactionLifecycleSteps` hook by converting several functions to use `useCallback`, enhancing performance and preventing unnecessary re-renders.
- This update improves the overall clarity and functionality of the Torus Bridge application by refining navigation and transaction lifecycle management.
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

Caution

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

⚠️ Outside diff range comments (1)
CLAUDE.md (1)

287-311: Resolve the merge conflict markers immediately.

Lines 287 onward still contain <<<<<<< HEAD/>>>>>>> origin/dev. This is unmergeable and will break the doc build. Resolve the conflict and keep only the intended content.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d41dc57 and 50b1f43.

📒 Files selected for processing (4)
  • CLAUDE.md (1 hunks)
  • apps/torus-bridge/src/app/_components/shared/sidebar-links.tsx (1 hunks)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (1 hunks)
  • apps/torus-bridge/src/app/simple/_components/use-transaction-lifecycle-steps.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/torus-bridge/src/app/simple/_components/use-transaction-lifecycle-steps.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-bridge/src/app/_components/shared/sidebar-links.tsx
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-bridge/src/app/_components/shared/sidebar-links.tsx
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.md: In Markdown, use sh (not bash) for shell snippets
In Markdown, use ts (not typescript) for TypeScript snippets

Files:

  • CLAUDE.md
🧠 Learnings (1)
📚 Learning: 2025-09-05T12:17:21.863Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-05T12:17:21.863Z
Learning: Applies to **/*.{ts,tsx} : Avoid top-level stateful singletons; use lazy initialization instead

Applied to files:

  • CLAUDE.md
🧬 Code graph analysis (2)
apps/torus-bridge/src/app/_components/shared/sidebar-links.tsx (2)
packages/ui/src/lib/utils.ts (1)
  • cn (5-7)
packages/ui/src/lib/data.ts (1)
  • getLinks (33-66)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (4)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (2)
  • SimpleBridgeDirection (3-3)
  • SimpleBridgeTransaction (59-69)
apps/torus-bridge/src/app/simple/_components/use-transaction-lifecycle-steps.ts (1)
  • useTransactionLifecycleSteps (20-263)
apps/torus-bridge/src/utils/logger.ts (1)
  • logger (1-8)
apps/torus-bridge/src/app/simple/_components/transaction-step-item.tsx (1)
  • TransactionStepItem (76-185)
⏰ 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). (8)
  • GitHub Check: torus-portal-pr-activate / clean-stale
  • GitHub Check: torus-governance-pr-activate / clean-stale
  • GitHub Check: torus-prophet-finder-pr-activate / clean-stale
  • GitHub Check: torus-bridge-pr-activate / clean-stale
  • GitHub Check: torus-wallet-pr-activate / clean-stale
  • GitHub Check: torus-page-pr-activate / clean-stale
  • GitHub Check: torus-cache-pr-activate / clean-stale
  • GitHub Check: torus-allocator-pr-activate / clean-stale

Comment on lines +83 to +92
const baseOptions = useMemo(
() => [
{
title: "Wallet",
href: getLinks(env("NEXT_PUBLIC_TORUS_CHAIN_ENV")).wallet,
},
{ title: "Simple Bridge", href: "/simple" },
{ title: "Full Bridge", href: "/" },
],
[],
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

Do not import env into client components.

~/env is explicitly guarded with if (typeof window !== "undefined") throw new Error("Do not import env.mjs in client-side code"). Because this file is marked "use client", bundles run in the browser and will immediately throw, breaking the sidebar entirely.

Fix: move the getLinks(env(...)) call into a server-only module/prop, or expose the needed wallet URL via a safe client-config (e.g., process.env.NEXT_PUBLIC_* constant imported without going through ~/env).

🤖 Prompt for AI Agents
In apps/torus-bridge/src/app/_components/shared/sidebar-links.tsx around lines
83-92, this client component currently calls
getLinks(env("NEXT_PUBLIC_TORUS_CHAIN_ENV")) which imports ~/env (guarded to
throw in the browser) and will break the client bundle; remove any direct import
of ~/env from this file and instead obtain the wallet URL from a server-safe
source: either call getLinks(env(...)) in a server module or page (or layout)
and pass the resulting wallet href into this client component as a prop, or
replace the call with a client-safe constant read from
process.env.NEXT_PUBLIC_TORUS_CHAIN_ENV (or a small client-config wrapper that
uses only NEXT_PUBLIC_ vars); ensure no ~/env import remains in this client
file.

Comment on lines +187 to +201
{(hasError || isCompleted) && (
<div className="bg-background flex shrink-0 justify-end gap-3 border-t px-6 py-4">
{hasError && onRetry && (
<Button onClick={onRetry} variant="outline">
Retry Transfer
</Button>
)}

{isCompleted && (
<Button onClick={onClose} variant="outline">
Close
</Button>
)}
</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

Trapped user when error occurs without retry callback.

If hasError is true but onRetry is not provided (line 29 makes it optional), the footer renders an empty action bar with no buttons:

  • Line 190: {hasError && onRetry && ...} evaluates to false, so no Retry button
  • Line 195: {isCompleted && ...} evaluates to false (error state), so no Close button
  • Lines 145-154: Escape key and outside clicks are blocked during errors

The user is trapped in an unclosable modal.

Apply this diff to always provide a close option during errors:

  {(hasError || isCompleted) && (
    <div className="bg-background flex shrink-0 justify-end gap-3 border-t px-6 py-4">
      {hasError && onRetry && (
        <Button onClick={onRetry} variant="outline">
          Retry Transfer
        </Button>
      )}

-     {isCompleted && (
+     {(isCompleted || (hasError && !onRetry)) && (
        <Button onClick={onClose} variant="outline">
          Close
        </Button>
      )}
    </div>
  )}

This ensures the Close button appears when errors occur without a retry handler, allowing users to dismiss the dialog.

📝 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
{(hasError || isCompleted) && (
<div className="bg-background flex shrink-0 justify-end gap-3 border-t px-6 py-4">
{hasError && onRetry && (
<Button onClick={onRetry} variant="outline">
Retry Transfer
</Button>
)}
{isCompleted && (
<Button onClick={onClose} variant="outline">
Close
</Button>
)}
</div>
)}
{(hasError || isCompleted) && (
<div className="bg-background flex shrink-0 justify-end gap-3 border-t px-6 py-4">
{hasError && onRetry && (
<Button onClick={onRetry} variant="outline">
Retry Transfer
</Button>
)}
{(isCompleted || (hasError && !onRetry)) && (
<Button onClick={onClose} variant="outline">
Close
</Button>
)}
</div>
)}
🤖 Prompt for AI Agents
In
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx
around lines 187 to 201, the footer can render empty when hasError is true but
onRetry is not provided, trapping the user; change the rendering so that when
hasError is true you always render a Close button (use onClose) even if onRetry
is missing (and render Retry only if onRetry exists), ensuring the modal remains
dismissible during error states by showing Close when either isCompleted or
hasError and onClose is available.

… handling

- Simplified the `DualWalletConnector` component by removing unnecessary connection logic and optimizing the rendering of wallet connection status.
- Updated the `TransactionLifecycleDialog` to conditionally render messages based on the environment, enhancing clarity during transaction processes.
- Refactored the `useDualWallet` hook to eliminate unused connection functions, improving code maintainability and readability.
- This update enhances the overall user experience and code organization within the Torus Bridge application by refining wallet connection management and transaction lifecycle handling.
- Removed unnecessary chain validation logic from the `DualWalletConnector` component, streamlining the connection status rendering.
- Updated the `useDualWallet` hook to eliminate unused parameters and simplify wallet readiness checks, enhancing code clarity and maintainability.
- This update improves the overall user experience and code organization within the Torus Bridge application by refining wallet connection management.
- Refactored the `DualWalletConnector`, `TransactionStepItem`, and various hooks to enhance code readability by adjusting formatting and spacing.
- Consolidated multi-line destructuring and conditional rendering for better clarity.
- This update improves overall code organization and maintainability within the Torus Bridge application by promoting consistent formatting practices.
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 (6)
apps/torus-bridge/src/app/simple/_components/use-transaction-lifecycle-steps.ts (1)

44-55: Still need to mark sign substeps completed on confirm errors

Confirm-phase failures still leave the matching *-sign substep stuck in "pending", so the UI tells users they must sign again even though signing already succeeded. Please treat confirm errors as "completed" for the corresponding sign substep (step1 & step2) so recovery flows stay accurate.

-          if (errorPhase === "confirm" && stepId === "step2-confirm")
-            return "error";
+          if (errorPhase === "confirm") {
+            if (stepId === "step2-confirm") return "error";
+            if (stepId === "step2-sign") return "completed";
+          }
@@
-        if (errorPhase === "confirm" && stepId === "step1-confirm")
-          return "error";
+        if (errorPhase === "confirm") {
+          if (stepId === "step1-confirm") return "error";
+          if (stepId === "step1-sign") return "completed";
+        }

Also applies to: 58-70

apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (4)

149-154: Bug: Chain verification uses stale chain.id in Step 1.

Line 152 passes () => Promise.resolve(chain.id) as getCurrentChainId, which never reflects the wallet's live chain state after a switch. This can cause false verification failures or allow execution on the wrong network.

This issue was flagged in a previous review but remains unresolved. Apply this fix:

  1. Add getCurrentChainId to BaseToNativeStep1Params:
 interface BaseToNativeStep1Params {
   ...
   switchChain: (params: { chainId: number }) => Promise<{ id: number }>;
+  /** Function to get the current wallet chain ID */
+  getCurrentChainId: () => Promise<number>;
   ...
 }
  1. Accept and use it in the function:
 export async function executeBaseToNativeStep1(
   params: BaseToNativeStep1Params,
 ) {
   const {
     ...
     switchChain,
+    getCurrentChainId,
     ...
   } = params;
   ...
   const switchResult = await switchChainWithRetry({
     targetChainId: BASE_CHAIN_ID,
     switchChain,
-    getCurrentChainId: () => Promise.resolve(chain.id),
+    getCurrentChainId,
     chainName: "Base",
   });
  1. Update the caller in use-simple-bridge-orchestrated-transfer.ts to pass:
getCurrentChainId: () => walletClient.getChainId(),

223-225: Bug: Baseline balance uses stale state from torusEvmBalance.

You await refetchTorusEvmBalance() but ignore its return value and read the potentially stale torusEvmBalance?.value prop. This can cause incorrect polling baselines if the prop hasn't updated yet.

This issue was flagged in a previous review but remains unresolved. Apply this diff:

-  await refetchTorusEvmBalance();
-  const baselineBalance = torusEvmBalance?.value ?? 0n;
+  const refetchResult = await refetchTorusEvmBalance();
+  const baselineBalance =
+    refetchResult.status === "error"
+      ? (torusEvmBalance?.value ?? 0n)
+      : (refetchResult.data?.value ?? 0n);
   const expectedIncrease = toNano(amount.trim());

475-477: Bug: Baseline Native balance uses stale state from nativeBalance.

You await refetchNativeBalance() but ignore its return value and read the potentially stale nativeBalance?.value prop. This can cause incorrect polling baselines if the prop hasn't updated yet.

This issue was flagged in a previous review but remains unresolved. Apply this diff:

-  await refetchNativeBalance();
-  const baselineNativeBalance = nativeBalance?.value ?? 0n;
+  const refetchNativeResult = await refetchNativeBalance();
+  const baselineNativeBalance =
+    refetchNativeResult.status === "error"
+      ? (nativeBalance?.value ?? 0n)
+      : (refetchNativeResult.data?.value ?? 0n);
   const expectedNativeIncrease = toNano(amount.trim());

516-516: Avoid empty explorer URL strings for Torus Native.

getExplorerUrl(txHash, "Torus Native") returns an empty string "" for Torus Native (as seen in the helper implementation), which can result in broken or empty links in the UI.

This issue was flagged in a previous review but remains unresolved. Apply this diff to coalesce empty strings to undefined:

   addTransaction({
     step: 2,
     status: "SUCCESS",
     chainName: "Torus Native",
     message: "Withdrawal complete",
     txHash,
-    explorerUrl: getExplorerUrl(txHash, "Torus Native"),
+    explorerUrl: getExplorerUrl(txHash, "Torus Native") || undefined,
   });

Note: Line 498 already handles this correctly for the error case.

apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts (1)

477-480: Use the refreshed Base balance and avoid parseFloat precision loss.

The current code discards the result of refetchBaseBalance() and falls back to the possibly stale baseBalance prop, plus it runs parseFloat(amount) before toNano, which can truncate decimal token amounts. Both issues can make the confirmation poll misfire or miss the expected increase.

-  await refetchBaseBalance();
-  const baseBaselineBalance = baseBalance?.value ?? 0n;
-  const baseExpectedIncrease = toNano(parseFloat(amount));
+  const baseRefreshResult = await refetchBaseBalance();
+  const baseBaselineBalance = baseRefreshResult.data?.value ?? 0n;
+  const baseExpectedIncrease = toNano(amount.trim());

(Update the refetchBaseBalance typing accordingly so data?.value is available without casts.)

🧹 Nitpick comments (4)
apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-dual-wallet.ts (2)

77-90: Refactor: isOnOptimalChain duplicates isRequiredChainConnected.

Both functions have identical implementations. Since they serve the same purpose (checking if the user is on the correct chain for the given direction), consolidate them into a single function or have one delegate to the other.

Apply this diff to eliminate the duplication:

-  const isOnOptimalChain = useCallback(
-    (direction: SimpleBridgeDirection) => {
-      if (!isEvmConnected || !chainId) {
-        return false;
-      }
-
-      if (direction === "base-to-native") {
-        return chainId === baseChainId;
-      }
-
-      return chainId === torusEvmChainId;
-    },
-    [isEvmConnected, chainId, baseChainId, torusEvmChainId],
-  );
+  const isOnOptimalChain = isRequiredChainConnected;

Then update the return statement:

   return {
     connectionState,
     isRequiredChainConnected,
-    isOnOptimalChain,
+    isOnOptimalChain: isRequiredChainConnected,
     getRequiredChainId,
     areWalletsReady,
     getConnectionStatus,
     chainIds: {
       base: baseChainId,
       torusEvm: torusEvmChainId,
     },
   };

103-111: Clarify: Unused _direction parameter in areWalletsReady.

The function accepts a _direction parameter but doesn't use it. If readiness is truly direction-agnostic, consider removing the parameter to simplify the API. If direction-specific logic is planned for the future, add a comment explaining the intent.

If direction doesn't matter, apply this diff:

-  const areWalletsReady = useCallback(
-    (_direction: SimpleBridgeDirection) => {
+  const areWalletsReady = useCallback(() => {
       const torusReady = connectionState.torusWallet.isConnected;
       const evmReady = connectionState.evmWallet.isConnected;
 
       return torusReady && evmReady;
     },
     [connectionState],
   );
apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts (1)

63-122: Consider optimizing retry wait times for better UX.

After a successful switchChain call (no error thrown), the code waits twice before the next retry:

  1. Lines 96-98: Wait 5s before verification with getCurrentChainId
  2. Lines 119-121: Wait another 5s before the next retry attempt

This results in a 10-second delay per failed attempt. With MAX_SWITCH_ATTEMPTS=3, users could wait up to 30+ seconds for all retries to complete, which may feel unresponsive.

Consider consolidating to a single wait between verification and retry, or reducing the delay for the verification wait to improve perceived responsiveness.

Example refactor:

     // Verify switch result
     if (switchResult.id === targetChainId) {
       console.log(`Successfully switched to ${chainName} chain`);
       return { success: true, isUserRejected: false };
     }
 
-    // Wait and verify with wallet
-    await new Promise((resolve) =>
-      setTimeout(resolve, POLLING_CONFIG.SWITCH_RETRY_DELAY_MS),
-    );
-
     const verifiedChainId = await getCurrentChainId();
     if (verifiedChainId === targetChainId) {
       console.log(`Chain switch verified after delay`);
       return { success: true, isUserRejected: false };
     }
 
     console.warn(
       `Chain ID mismatch after switch. Expected: ${targetChainId}, Got: ${verifiedChainId}`,
     );
 
     // Check if max attempts reached
     if (attempts >= POLLING_CONFIG.MAX_SWITCH_ATTEMPTS) {
       lastError = new Error(
         `Chain switch verification failed. Expected ${targetChainId}, got ${verifiedChainId}`,
       );
       break;
     }
 
     // Wait before retry
     await new Promise((resolve) =>
       setTimeout(resolve, POLLING_CONFIG.SWITCH_RETRY_DELAY_MS),
     );
apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts (1)

321-321: Tighten refetchBaseBalance typing to remove the unsafe cast.

refetchBaseBalance is declared as Promise<unknown> and later force-cast before polling. Give it the concrete balance result type (same shape used in Step 1) so the compiler can verify consumers and you can drop the as () => Promise<{ status: string; data?: { value: bigint } }> cast.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cee76c5 and d6e4fcb.

📒 Files selected for processing (8)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-dual-wallet-connector.tsx (1 hunks)
  • apps/torus-bridge/src/app/simple/_components/transaction-step-item.tsx (1 hunks)
  • apps/torus-bridge/src/app/simple/_components/use-transaction-lifecycle-steps.ts (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-polling.ts (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-dual-wallet.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/torus-bridge/src/app/simple/_components/transaction-step-item.tsx
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-dual-wallet-connector.tsx
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-polling.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-dual-wallet.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts
  • apps/torus-bridge/src/app/simple/_components/use-transaction-lifecycle-steps.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-dual-wallet.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts
  • apps/torus-bridge/src/app/simple/_components/use-transaction-lifecycle-steps.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
🧠 Learnings (2)
📚 Learning: 2025-09-05T12:17:21.863Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-05T12:17:21.863Z
Learning: Applies to **/*.{ts,tsx} : Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation

Applied to files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
📚 Learning: 2025-07-27T01:55:40.478Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: packages/torus-sdk-ts/src/chain/CLAUDE.md:0-0
Timestamp: 2025-07-27T01:55:40.478Z
Learning: Applies to packages/torus-sdk-ts/src/chain/**/*.{ts,tsx} : Document all exports with JSDoc comments.

Applied to files:

  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts
  • apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts
🧬 Code graph analysis (5)
apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-dual-wallet.ts (4)
packages/torus-provider/src/torus-provider.tsx (1)
  • useTorus (266-272)
apps/torus-bridge/src/config.ts (1)
  • getChainValuesOnEnv (58-65)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (2)
  • WalletConnectionState (35-50)
  • SimpleBridgeDirection (3-3)
packages/torus-sdk-ts/src/types/address.ts (1)
  • SS58Address (37-37)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts (8)
packages/torus-sdk-ts/src/types/address.ts (1)
  • SS58Address (37-37)
packages/torus-sdk-ts/src/chain/balances.ts (1)
  • transferAllowDeath (78-84)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (1)
  • SimpleBridgeTransaction (59-69)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-polling.ts (1)
  • pollEvmBalance (146-159)
packages/torus-utils/src/try-catch.ts (1)
  • tryAsync (97-101)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts (5)
  • withTimeout (82-93)
  • TIMEOUT_CONFIG (66-69)
  • isUserRejectionError (100-117)
  • formatErrorForUser (149-241)
  • UserRejectedError (53-58)
packages/torus-sdk-ts/src/evm.ts (1)
  • convertH160ToSS58 (22-42)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts (2)
  • switchChainWithRetry (48-142)
  • throwOnChainSwitchFailure (151-161)
apps/torus-bridge/src/app/simple/_components/use-transaction-lifecycle-steps.ts (1)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (2)
  • SimpleBridgeDirection (3-3)
  • SimpleBridgeTransaction (59-69)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts (2)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts (4)
  • POLLING_CONFIG (30-35)
  • isUserRejectionError (100-117)
  • formatErrorForUser (149-241)
  • UserRejectedError (53-58)
packages/torus-utils/src/try-catch.ts (1)
  • tryAsync (97-101)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-base-to-native-flow.ts (6)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (1)
  • SimpleBridgeTransaction (59-69)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-helpers.ts (6)
  • BASE_CHAIN_ID (5-5)
  • isUserRejectionError (100-117)
  • formatErrorForUser (149-241)
  • UserRejectedError (53-58)
  • getExplorerUrl (126-143)
  • CONFIRMATION_CONFIG (42-44)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts (2)
  • switchChainWithRetry (48-142)
  • throwOnChainSwitchFailure (151-161)
packages/torus-utils/src/try-catch.ts (1)
  • tryAsync (97-101)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-polling.ts (1)
  • pollEvmBalance (146-159)
packages/torus-sdk-ts/src/evm.ts (1)
  • withdrawFromTorusEvm (66-131)
⏰ 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: torus-worker-dry-run / Check for existing PR
  • GitHub Check: code-quality-summary
🔇 Additional comments (3)
apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts (3)

1-7: LGTM!

Imports are clean and follow the [error, data] tuple pattern from tryAsync as required by the coding guidelines.


9-35: LGTM!

Type definitions are clear and well-documented with JSDoc comments per the coding guidelines.


151-161: LGTM!

Error handling logic correctly distinguishes user rejections from other failures and throws appropriate error types.

Comment on lines +124 to +142
// Handle failure after all attempts
const isUserRejected =
lastError !== undefined && isUserRejectionError(lastError);
const errorMessage = isUserRejected
? "Network switch was not accepted"
: `Unable to switch to ${chainName} network`;

const errorDetails = isUserRejected
? `Please accept the network switch in your wallet to continue the transfer, or switch manually to ${chainName} and click Retry.`
: `Failed to switch to ${chainName} network after ${POLLING_CONFIG.MAX_SWITCH_ATTEMPTS} attempts. Please switch manually to ${chainName} in your wallet and click Retry to continue.`;

return {
success: false,
errorMessage,
errorDetails:
lastError !== undefined ? formatErrorForUser(lastError) : errorDetails,
isUserRejected,
};
}
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

Context-aware error messages are being replaced with generic formatting.

Lines 131-133 construct detailed error messages with actionable guidance (e.g., "switch manually to X and click Retry"). However, line 139 replaces errorDetails with formatErrorForUser(lastError) when lastError exists, discarding this context.

formatErrorForUser provides user-friendly translations of technical errors, but doesn't include the retry instructions and context that users need to recover from a failed chain switch.

Recommendation:

Keep the context-aware messages and append the formatted error details if needed:

+  const formattedError = lastError !== undefined ? formatErrorForUser(lastError) : undefined;
+
   return {
     success: false,
     errorMessage,
-    errorDetails:
-      lastError !== undefined ? formatErrorForUser(lastError) : errorDetails,
+    errorDetails: formattedError 
+      ? `${errorDetails}\n\nTechnical details: ${formattedError}`
+      : errorDetails,
     isUserRejected,
   };

Alternatively, if formatErrorForUser already provides sufficient guidance, document why the context-aware messages are intentionally discarded.

📝 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
// Handle failure after all attempts
const isUserRejected =
lastError !== undefined && isUserRejectionError(lastError);
const errorMessage = isUserRejected
? "Network switch was not accepted"
: `Unable to switch to ${chainName} network`;
const errorDetails = isUserRejected
? `Please accept the network switch in your wallet to continue the transfer, or switch manually to ${chainName} and click Retry.`
: `Failed to switch to ${chainName} network after ${POLLING_CONFIG.MAX_SWITCH_ATTEMPTS} attempts. Please switch manually to ${chainName} in your wallet and click Retry to continue.`;
return {
success: false,
errorMessage,
errorDetails:
lastError !== undefined ? formatErrorForUser(lastError) : errorDetails,
isUserRejected,
};
}
// Handle failure after all attempts
const isUserRejected =
lastError !== undefined && isUserRejectionError(lastError);
const errorMessage = isUserRejected
? "Network switch was not accepted"
: `Unable to switch to ${chainName} network`;
const errorDetails = isUserRejected
? `Please accept the network switch in your wallet to continue the transfer, or switch manually to ${chainName} and click Retry.`
: `Failed to switch to ${chainName} network after ${POLLING_CONFIG.MAX_SWITCH_ATTEMPTS} attempts. Please switch manually to ${chainName} in your wallet and click Retry to continue.`;
const formattedError = lastError !== undefined ? formatErrorForUser(lastError) : undefined;
return {
success: false,
errorMessage,
- errorDetails:
errorDetails: formattedError
? `${errorDetails}\n\nTechnical details: ${formattedError}`
: errorDetails,
isUserRejected,
};
}
🤖 Prompt for AI Agents
In apps/torus-bridge/src/app/simple/hooks/simple-bridge-chain-switch.ts around
lines 124-142, the context-aware errorDetails (which include actionable retry
instructions) are being discarded when lastError exists because errorDetails is
replaced with formatErrorForUser(lastError); preserve the contextual guidance by
appending the formatted error to the existing context-aware message when
lastError is present (e.g., if lastError exists set errorDetails =
`${errorDetails}\n\n${formatErrorForUser(lastError)}`), otherwise leave the
original errorDetails; ensure both user-rejection and generic failure branches
keep their respective guidance and only augment with the formatted technical
message when available.

Comment on lines +278 to +280
await refetchTorusEvmBalance();
const baselineBalance = torusEvmBalance?.value ?? 0n;
const expectedIncrease = toNano(amount.trim());
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

Fix Torus EVM baseline to use the fresh refetch result.

Line 279 currently reuses the stale torusEvmBalance prop even after calling refetchTorusEvmBalance(), so polling can compare against the wrong baseline and falsely fail (e.g., when the prop was higher than the freshly fetched balance). Capture and use the refetch return value instead.

-  await refetchTorusEvmBalance();
-  const baselineBalance = torusEvmBalance?.value ?? 0n;
+  const baselineResult = await refetchTorusEvmBalance();
+  const baselineBalance = baselineResult.data?.value ?? 0n;
📝 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
await refetchTorusEvmBalance();
const baselineBalance = torusEvmBalance?.value ?? 0n;
const expectedIncrease = toNano(amount.trim());
const baselineResult = await refetchTorusEvmBalance();
const baselineBalance = baselineResult.data?.value ?? 0n;
const expectedIncrease = toNano(amount.trim());
🤖 Prompt for AI Agents
In apps/torus-bridge/src/app/simple/hooks/simple-bridge-native-to-base-flow.ts
around lines 278 to 280, after calling refetchTorusEvmBalance() the code still
reads the stale torusEvmBalance prop for the baseline; change it to capture and
use the refetch return value (e.g., await refetchTorusEvmBalance() result) and
derive baselineBalance from that result's data.value (falling back to 0n) so the
polling compares against the freshly fetched balance.

- Introduced a new `TransactionStepItem` component to improve the rendering of transaction steps within the `TransactionLifecycleDialog`, enhancing code organization and readability.
- Implemented the `useTransactionLifecycleSteps` hook to encapsulate transaction lifecycle logic, promoting reusability and separation of concerns.
- Updated the `TransactionLifecycleDialog` to utilize the new component and hook, simplifying its structure and improving maintainability.
- This update enhances the overall clarity and robustness of the Torus Bridge application by refining transaction lifecycle management and promoting better coding practices.
- Replaced YAML configuration files with JSON equivalents for chains and warp routes, enhancing consistency and ease of use.
- Updated the `assembleWarpCoreConfig` and `assembleChainMetadata` functions to parse JSON data instead of YAML, improving performance and reducing dependency on YAML loaders.
- This update streamlines configuration management within the Torus Bridge application, promoting better practices and simplifying future modifications.
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

♻️ Duplicate comments (1)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (1)

189-202: Restore a Close path when errors lack retry.

With hasError true and onRetry undefined, the footer renders nothing while ESC/outside clicks are blocked, trapping the user in the dialog. Always expose a Close button (or re-enable default dismissal) for this case.

-        {(hasError || isCompleted) && (
+        {(hasError || isCompleted) && (
           <div className="bg-background flex shrink-0 justify-end gap-3 border-t px-6 py-4">
             {hasError && onRetry && (
               <Button onClick={onRetry} variant="outline">
                 Retry Transfer
               </Button>
             )}
 
-            {isCompleted && (
+            {(isCompleted || (hasError && !onRetry)) && (
               <Button onClick={onClose} variant="outline">
                 Close
               </Button>
             )}
           </div>
         )}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d6e4fcb and ddc8509.

📒 Files selected for processing (3)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (1 hunks)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-step-item.tsx (1 hunks)
  • apps/torus-bridge/src/app/simple/hooks/use-transaction-lifecycle-steps.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-bridge/src/app/simple/hooks/use-transaction-lifecycle-steps.ts
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-step-item.tsx
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-bridge/src/app/simple/hooks/use-transaction-lifecycle-steps.ts
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-step-item.tsx
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx
🧬 Code graph analysis (2)
apps/torus-bridge/src/app/simple/hooks/use-transaction-lifecycle-steps.ts (1)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (2)
  • SimpleBridgeDirection (3-3)
  • SimpleBridgeTransaction (59-69)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (4)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (2)
  • SimpleBridgeDirection (3-3)
  • SimpleBridgeTransaction (59-69)
apps/torus-bridge/src/app/simple/hooks/use-transaction-lifecycle-steps.ts (1)
  • useTransactionLifecycleSteps (23-281)
apps/torus-bridge/src/utils/logger.ts (1)
  • logger (1-8)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-step-item.tsx (1)
  • TransactionStepItem (76-189)
⏰ 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: turbo-check / run

Comment on lines +70 to +74
function getNetworkName(title: string) {
if (title.includes("Base")) return "Base";
if (title.includes("Native")) return "Torus Native";
return "Torus EVM";
}
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

Fix network detection for confirmation step.

For the Base→Native flow, the “Withdrawal Confirming” row never matches “Base” or “Native”, so the accordion reports “Torus EVM” even though the confirmation happens on Torus Native. Please derive the network name from the full step context (e.g., include the description) so it stays accurate when titles change.

-function getNetworkName(title: string) {
-  if (title.includes("Base")) return "Base";
-  if (title.includes("Native")) return "Torus Native";
-  return "Torus EVM";
-}
+function getNetworkName(title: string, description?: string) {
+  const context = `${title} ${description ?? ""}`;
+  if (context.includes("Base")) return "Base";
+  if (context.includes("Native")) return "Torus Native";
+  return "Torus EVM";
+}-                  Network: <strong>{getNetworkName(title)}</strong>
+                  Network: <strong>{getNetworkName(title, description)}</strong>

Also applies to: 155-163

Comment on lines +170 to +181
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation();
window.open(explorerUrl, "_blank");
}}
className="mt-2 w-full justify-start"
>
<ExternalLink className="mr-1 h-3 w-3" />
View on Explorer
</Button>
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

Harden explorer link against reverse tabnabbing.

Opening third-party explorers without noopener lets the child window control the opener tab. Please add the security flags.

-                    window.open(explorerUrl, "_blank");
+                    window.open(explorerUrl, "_blank", "noopener,noreferrer");
📝 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
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation();
window.open(explorerUrl, "_blank");
}}
className="mt-2 w-full justify-start"
>
<ExternalLink className="mr-1 h-3 w-3" />
View on Explorer
</Button>
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation();
window.open(explorerUrl, "_blank", "noopener,noreferrer");
}}
className="mt-2 w-full justify-start"
>
<ExternalLink className="mr-1 h-3 w-3" />
View on Explorer
</Button>
🤖 Prompt for AI Agents
In
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-step-item.tsx
around lines 170 to 181, the explorer link opened with window.open(explorerUrl,
"_blank") is vulnerable to reverse tabnabbing; update the click handler to open
the URL with security flags by using window.open(explorerUrl, "_blank",
"noopener,noreferrer") or, if you prefer, set the opened window's opener to null
(const w = window.open(...); if (w) w.opener = null) so the child cannot access
or manipulate the opener.

Comment on lines +38 to +70
if (step2Transaction?.status === "ERROR") {
if (isStep1) {
return "completed";
}

if (isStep2) {
const errorPhase = step2Transaction.errorPhase;
if (errorPhase === "sign" && stepId === "step2-sign") return "error";
if (errorPhase === "confirm" && stepId === "step2-confirm")
return "error";

if (!errorPhase) {
if (stepId === "step2-sign") return "error";
if (stepId === "step2-confirm") return "pending";
}

return "pending";
}
}

if (step1Transaction?.status === "ERROR" && isStep1) {
const errorPhase = step1Transaction.errorPhase;
if (errorPhase === "sign" && stepId === "step1-sign") return "error";
if (errorPhase === "confirm" && stepId === "step1-confirm")
return "error";

if (!errorPhase) {
if (stepId === "step1-sign") return "error";
if (stepId === "step1-confirm") return "pending";
}

return "pending";
}
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

Keep signing rows completed on confirmation failures.

When errorPhase is "confirm", the signing substep already finished, but the fallback return "pending" marks it as incomplete. That regresses the timeline and confuses users.

         if (isStep2) {
           const errorPhase = step2Transaction.errorPhase;
           if (errorPhase === "sign" && stepId === "step2-sign") return "error";
-          if (errorPhase === "confirm" && stepId === "step2-confirm")
-            return "error";
+          if (errorPhase === "confirm") {
+            if (stepId === "step2-confirm") return "error";
+            if (stepId === "step2-sign") return "completed";
+          }
…
       if (step1Transaction?.status === "ERROR" && isStep1) {
         const errorPhase = step1Transaction.errorPhase;
         if (errorPhase === "sign" && stepId === "step1-sign") return "error";
-        if (errorPhase === "confirm" && stepId === "step1-confirm")
-          return "error";
+        if (errorPhase === "confirm") {
+          if (stepId === "step1-confirm") return "error";
+          if (stepId === "step1-sign") return "completed";
+        }
📝 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
if (step2Transaction?.status === "ERROR") {
if (isStep1) {
return "completed";
}
if (isStep2) {
const errorPhase = step2Transaction.errorPhase;
if (errorPhase === "sign" && stepId === "step2-sign") return "error";
if (errorPhase === "confirm" && stepId === "step2-confirm")
return "error";
if (!errorPhase) {
if (stepId === "step2-sign") return "error";
if (stepId === "step2-confirm") return "pending";
}
return "pending";
}
}
if (step1Transaction?.status === "ERROR" && isStep1) {
const errorPhase = step1Transaction.errorPhase;
if (errorPhase === "sign" && stepId === "step1-sign") return "error";
if (errorPhase === "confirm" && stepId === "step1-confirm")
return "error";
if (!errorPhase) {
if (stepId === "step1-sign") return "error";
if (stepId === "step1-confirm") return "pending";
}
return "pending";
}
if (step2Transaction?.status === "ERROR") {
if (isStep1) {
return "completed";
}
if (isStep2) {
const errorPhase = step2Transaction.errorPhase;
if (errorPhase === "sign" && stepId === "step2-sign") return "error";
if (errorPhase === "confirm") {
if (stepId === "step2-confirm") return "error";
if (stepId === "step2-sign") return "completed";
}
if (!errorPhase) {
if (stepId === "step2-sign") return "error";
if (stepId === "step2-confirm") return "pending";
}
return "pending";
}
}
if (step1Transaction?.status === "ERROR" && isStep1) {
const errorPhase = step1Transaction.errorPhase;
if (errorPhase === "sign" && stepId === "step1-sign") return "error";
if (errorPhase === "confirm") {
if (stepId === "step1-confirm") return "error";
if (stepId === "step1-sign") return "completed";
}
if (!errorPhase) {
if (stepId === "step1-sign") return "error";
if (stepId === "step1-confirm") return "pending";
}
return "pending";
}
🤖 Prompt for AI Agents
In apps/torus-bridge/src/app/simple/hooks/use-transaction-lifecycle-steps.ts
around lines 38–70, the logic marks the signing substep as "pending" when
errorPhase === "confirm", but the signing step was already completed; update
both the step2 and step1 error blocks so that when errorPhase === "confirm" and
stepId corresponds to the "-sign" substep you return "completed" (keep the
existing behavior of returning "error" for the "-confirm" substep and preserve
the existing fallback behavior when errorPhase is falsy).

…oader from bridge application

- Updated the version specifications for several dependencies in `pnpm-lock.yaml`, including the addition of `@babel/core` to the `@next/third-parties` package.
- Removed the `yaml-loader` dependency from the `torus-bridge` application's `package.json`, streamlining the project by eliminating unnecessary packages.
- These changes enhance the overall dependency management and maintainability of the Torus Bridge application.
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

🧹 Nitpick comments (3)
apps/torus-bridge/next.config.mjs (1)

21-21: LGTM! Removal of YAML loader config aligns with JSON migration.

The simplification to an empty turbopack object is consistent with the PR's broader migration from YAML to JSON configurations.

If no turbopack configuration is needed, you can optionally remove this line entirely:

-  turbopack: {},

However, keeping the empty object is fine if you anticipate adding turbopack configuration in the future or prefer it for documentation purposes.

apps/torus-bridge/src/consts/chains.json (1)

31-35: Consider adding multiple RPC URLs for redundancy.

The configuration includes only a single RPC endpoint. If this endpoint becomes unavailable, all bridge operations will fail.

Consider adding fallback RPC URLs to improve reliability:

 "rpcUrls": [
   {
     "http": "https://api-hyperlane.nodes.torus.network"
-  }
+  },
+  {
+    "http": "https://backup-rpc.nodes.torus.network"
+  }
 ],
apps/torus-bridge/src/app/_components/warp-core-config.ts (1)

1-3: Consider addressing the TypeScript errors.

Since this PR is marked as WIP, it might be a good opportunity to resolve the TypeScript errors and remove the @ts-nocheck directive. This would improve type safety and catch potential issues early.

Would you like me to help identify the TypeScript errors or open a new issue to track this task?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ddc8509 and 6cd7b1b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • apps/torus-bridge/next.config.mjs (1 hunks)
  • apps/torus-bridge/package.json (0 hunks)
  • apps/torus-bridge/src/app/_components/chains/chain-metadata.ts (2 hunks)
  • apps/torus-bridge/src/app/_components/warp-core-config.ts (3 hunks)
  • apps/torus-bridge/src/consts/chains.json (1 hunks)
  • apps/torus-bridge/src/consts/chains.yaml (0 hunks)
  • apps/torus-bridge/src/consts/warp-routes.json (1 hunks)
  • apps/torus-bridge/src/consts/warp-routes.yaml (0 hunks)
💤 Files with no reviewable changes (3)
  • apps/torus-bridge/src/consts/chains.yaml
  • apps/torus-bridge/src/consts/warp-routes.yaml
  • apps/torus-bridge/package.json
✅ Files skipped from review due to trivial changes (1)
  • apps/torus-bridge/src/consts/warp-routes.json
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-bridge/src/app/_components/chains/chain-metadata.ts
  • apps/torus-bridge/src/app/_components/warp-core-config.ts
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-bridge/src/app/_components/chains/chain-metadata.ts
  • apps/torus-bridge/src/app/_components/warp-core-config.ts
🧠 Learnings (1)
📚 Learning: 2025-07-27T01:55:40.478Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: packages/torus-sdk-ts/src/chain/CLAUDE.md:0-0
Timestamp: 2025-07-27T01:55:40.478Z
Learning: Applies to packages/torus-sdk-ts/src/chain/**/*.{ts,tsx} : Implement robust error handling in query functions using `tryAsync` and `trySync` wrappers.

Applied to files:

  • apps/torus-bridge/src/app/_components/chains/chain-metadata.ts
⏰ 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). (9)
  • GitHub Check: torus-portal-pr-activate / clean-stale
  • GitHub Check: torus-wallet-pr-activate / clean-stale
  • GitHub Check: torus-prophet-finder-pr-activate / clean-stale
  • GitHub Check: torus-governance-pr-activate / clean-stale
  • GitHub Check: torus-page-pr-activate / clean-stale
  • GitHub Check: torus-cache-pr-activate / clean-stale
  • GitHub Check: torus-allocator-pr-activate / clean-stale
  • GitHub Check: torus-bridge-dry-run / Check for existing PR
  • GitHub Check: check
🔇 Additional comments (4)
apps/torus-bridge/src/consts/chains.json (2)

30-36: Verify the protocol and technicalStack consistency.

The configuration specifies protocol: "ethereum" but technicalStack: "polkadotsubstrate". This appears inconsistent—if Torus runs on a Polkadot Substrate-based stack, the protocol field may need adjustment, or the technicalStack may be incorrect.

Please confirm which is correct and update accordingly. If this is intentional (e.g., Ethereum-compatible RPC on Substrate), consider adding a comment explaining the rationale.


23-23: Confirmed CoinGecko ID
The gasCurrencyCoinGeckoId value "torus" is valid and returns the expected data from CoinGecko. No changes needed.

apps/torus-bridge/src/app/_components/chains/chain-metadata.ts (1)

8-8: LGTM! JSON migration with proper validation.

The change from YAML to JSON is clean and maintains the existing error handling pattern. The type assertion on line 21 is acceptable because the subsequent zod schema validation (line 20) will catch any structural mismatches between the JSON and the expected ChainMetadata schema.

Based on coding guidelines: The error handling correctly uses trySync as specified for this codebase.

Also applies to: 19-24

apps/torus-bridge/src/app/_components/warp-core-config.ts (1)

11-11: Approve JSON config migration. Verified warp-routes.json exists and no stale YAML references remain.

…components

- Renamed state variable `amount` to `amountFrom` for clarity in the `SimpleBridgeForm` component, enhancing code readability.
- Introduced `InputReadonly` component for displaying read-only values, improving the user interface for the "From" and "To" fields.
- Updated transaction handling logic to ensure proper state management and validation checks for the new input structure.
- Enhanced button text and transaction description based on the transfer direction, providing clearer user feedback during operations.
- This update improves the overall user experience and maintainability of the Torus Bridge application by refining form handling and UI components.
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 (2)
packages/ui/src/components/input.tsx (2)

4-10: Add JSDoc documentation for new interfaces.

The coding guidelines require JSDoc conventions for all code documentation. Please add JSDoc comments to BaseInputProps and InputProps describing their purpose and usage.

As per coding guidelines.

Apply this diff to add JSDoc documentation:

+/**
+ * Base props shared across input components.
+ */
 interface BaseInputProps {
   label?: string;
 }
 
+/**
+ * Props for the Input component.
+ * Extends standard HTML input attributes with optional label support.
+ */
 export interface InputProps
   extends React.InputHTMLAttributes<HTMLInputElement>,
     BaseInputProps {}

42-67: Consider accessibility improvements and documentation.

The InputReadonly component would benefit from:

  1. JSDoc documentation (per coding guidelines):

    /**
     * A readonly input display component that renders a non-editable field.
     * Useful for displaying values that look like inputs but aren't interactive.
     */
  2. Accessibility attributes for screen readers to properly identify this as a readonly field:

    role="textbox"
    aria-readonly="true"
  3. Label positioning consistency: The component uses right-3 for label positioning (line 57) while the Input component uses right-8 (line 32). Consider whether this visual difference is intentional or if consistency is preferred.

As per coding guidelines.

Apply this diff to add JSDoc and accessibility attributes:

+/**
+ * A readonly input display component that renders a non-editable field.
+ * Useful for displaying values that look like inputs but aren't interactive.
+ */
 const InputReadonly = React.forwardRef<HTMLDivElement, InputReadonlyProps>(
   ({ className, label, value, disabled, ...props }, ref) => {
     return (
       <div className="relative w-full">
         <div
           className={cn(
             "rounded-radius flex h-9 w-full items-center border border-border bg-field-background px-3 py-1 text-sm shadow-sm transition-colors",
             disabled && "cursor-not-allowed opacity-50",
             className,
           )}
+          role="textbox"
+          aria-readonly="true"
           ref={ref}
           {...props}
         >

For label positioning consistency, consider changing right-3 to right-8 on line 57 to match the Input component, unless the tighter spacing is intentional for the readonly variant.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6cd7b1b and 6d7aa82.

📒 Files selected for processing (3)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx (1 hunks)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-fraction-buttons.tsx (1 hunks)
  • packages/ui/src/components/input.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-fraction-buttons.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • packages/ui/src/components/input.tsx
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • packages/ui/src/components/input.tsx
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx
🧬 Code graph analysis (2)
packages/ui/src/components/input.tsx (1)
packages/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx (9)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (1)
  • SimpleBridgeDirection (3-3)
apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-dual-wallet.ts (1)
  • useDualWallet (14-143)
apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-orchestrated-transfer.ts (1)
  • useOrchestratedTransfer (35-534)
packages/torus-provider/src/torus-provider.tsx (1)
  • useTorus (266-272)
packages/query-provider/src/hooks.ts (1)
  • useFreeBalance (132-143)
apps/torus-bridge/src/config.ts (1)
  • contractAddresses (39-48)
apps/torus-bridge/src/app/simple/_components/simple-bridge-dual-wallet-connector.tsx (1)
  • DualWalletConnector (18-155)
apps/torus-bridge/src/app/simple/_components/simple-bridge-fraction-buttons.tsx (1)
  • FractionButtons (41-83)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (1)
  • TransactionLifecycleDialog (71-207)
⏰ 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). (10)
  • GitHub Check: torus-cache-pr-activate / clean-stale
  • GitHub Check: torus-prophet-finder-pr-activate / clean-stale
  • GitHub Check: torus-bridge-pr-activate / clean-stale
  • GitHub Check: torus-wallet-pr-activate / clean-stale
  • GitHub Check: torus-page-pr-activate / clean-stale
  • GitHub Check: torus-portal-pr-activate / clean-stale
  • GitHub Check: torus-allocator-pr-activate / clean-stale
  • GitHub Check: torus-governance-pr-activate / clean-stale
  • GitHub Check: typecheck
  • GitHub Check: lint

Comment on lines +51 to +59
const { data: baseBalance } = useReadContract({
chainId: chainIds.base,
address: baseTorusAddress,
abi: erc20Abi,
functionName: "balanceOf",
args: connectionState.evmWallet.address
? [connectionState.evmWallet.address as `0x${string}`]
: undefined,
});
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

Guard the ERC20 balance query until the wallet address exists.

When the component mounts before the EVM wallet connects, connectionState.evmWallet.address is undefined, yet useReadContract still executes balanceOf with zero arguments. Viem rejects this with InvalidArgumentsError, so the query keeps retrying and the hook surfaces an error state even though nothing is actually wrong. Add an enabled guard (and reuse the derived address) so the call only fires once an address is present.

+  const evmAddress = connectionState.evmWallet.address as
+    | `0x${string}`
+    | undefined;
+
   const { data: baseBalance } = useReadContract({
     chainId: chainIds.base,
     address: baseTorusAddress,
     abi: erc20Abi,
     functionName: "balanceOf",
-    args: connectionState.evmWallet.address
-      ? [connectionState.evmWallet.address as `0x${string}`]
-      : undefined,
+    args: evmAddress ? [evmAddress] : undefined,
+    query: {
+      enabled: Boolean(evmAddress),
+    },
   });
📝 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 { data: baseBalance } = useReadContract({
chainId: chainIds.base,
address: baseTorusAddress,
abi: erc20Abi,
functionName: "balanceOf",
args: connectionState.evmWallet.address
? [connectionState.evmWallet.address as `0x${string}`]
: undefined,
});
const evmAddress = connectionState.evmWallet.address as
| `0x${string}`
| undefined;
const { data: baseBalance } = useReadContract({
chainId: chainIds.base,
address: baseTorusAddress,
abi: erc20Abi,
functionName: "balanceOf",
args: evmAddress ? [evmAddress] : undefined,
query: {
enabled: Boolean(evmAddress),
},
});
🤖 Prompt for AI Agents
In apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx around
lines 51–59, the ERC20 balance query runs before the EVM wallet address exists
causing InvalidArgumentsError; derive the address into a local const (e.g. const
evmAddress = connectionState.evmWallet.address as `0x${string}` | undefined),
use that for the args (args: evmAddress ? [evmAddress] : undefined) and add an
enabled guard (enabled: Boolean(evmAddress)) to the useReadContract call so the
hook only fires once an address is present.

Comment on lines +12 to +16
export interface InputReadonlyProps extends BaseInputProps {
value: React.ReactNode;
className?: string;
disabled?: boolean;
}
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

Extend React.HTMLAttributes<HTMLDivElement> for consistency and extensibility.

InputReadonlyProps should extend React.HTMLAttributes<HTMLDivElement> to:

  1. Maintain consistency with the Input component pattern (which extends React.InputHTMLAttributes)
  2. Allow callers to pass standard div attributes like aria-*, data-*, id, role, etc.
  3. Make the {...props} spreading on line 53 functional (currently it's a no-op since all defined props are destructured)

Additionally, add JSDoc documentation per coding guidelines.

As per coding guidelines.

Apply this diff:

+/**
+ * Props for the InputReadonly component.
+ * Extends standard HTML div attributes with readonly input display support.
+ */
 export interface InputReadonlyProps
-  extends BaseInputProps {
+  extends React.HTMLAttributes<HTMLDivElement>,
+    BaseInputProps {
   value: React.ReactNode;
-  className?: string;
-  disabled?: boolean;
+  disabled?: boolean; // Note: className is now inherited from HTMLAttributes
 }

Note: className is already provided by HTMLAttributes, so it can be removed from the explicit declaration.

📝 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
export interface InputReadonlyProps extends BaseInputProps {
value: React.ReactNode;
className?: string;
disabled?: boolean;
}
/**
* Props for the InputReadonly component.
* Extends standard HTML div attributes with readonly input display support.
*/
export interface InputReadonlyProps
extends React.HTMLAttributes<HTMLDivElement>,
BaseInputProps {
value: React.ReactNode;
disabled?: boolean; // Note: className is now inherited from HTMLAttributes
}
🤖 Prompt for AI Agents
packages/ui/src/components/input.tsx around lines 12 to 16: The
InputReadonlyProps interface should extend React.HTMLAttributes<HTMLDivElement>
(not just BaseInputProps) so callers can pass standard div attributes and the
{...props} spread on line 53 becomes functional; remove the redundant className
declaration (inherited from HTMLAttributes), keep other props (value, disabled)
and update the extends clause to include React.HTMLAttributes<HTMLDivElement>,
and add a short JSDoc comment above the interface per coding guidelines
describing the component props and purpose.

…omponents and improved state management

- Introduced `APRBarClient` and related components to display APR and USD price information, enhancing the user interface for better financial insights.
- Updated `SimpleBridgeForm` to utilize the new `TorusToUSD` component for displaying converted values, improving clarity in balance representation.
- Removed the `WalletConflictGuard` component to streamline wallet management, simplifying the application structure.
- Enhanced state management in the `staking-calculator` and `simple-bridge-form` components, ensuring better performance and user experience.
- These updates improve the overall functionality and maintainability of the Torus Bridge and Wallet applications, providing users with clearer financial data and a more intuitive interface.
- Updated the `SimpleBridgeForm` component to adjust layout styles for better responsiveness.
- Refactored the `staking-calculator` component to import the `useAPR` hook, improving APR data handling.
- Modified the `apr-bar-base` component to optimize animation and layout properties, enhancing visual presentation.
- These changes improve the overall user experience and maintainability of the Torus Bridge and Wallet applications by refining UI components and their interactions.
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: 4

Caution

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

⚠️ Outside diff range comments (2)
packages/query-provider/src/hooks.ts (1)

461-505: Harden Coingecko parsing and fetch policy

  • Guard data.torus?.usd with optional chaining and finite-number check.
  • Add staleTime: 60_000 and refetchOnWindowFocus: false.
  • Optionally implement fetch timeout via AbortController.
  • Expand JSDoc with @param and @returns.

Apply this diff:

@@ export function useGetTorusPrice(
-/**
- * Get the current Torus price in USD from Coingecko API
- */
+/**
+ * Get the current Torus price in USD from Coingecko API.
+ *
+ * @param options Optional React Query options.
+ * @returns UseQueryResult<number, Error> with USD price.
+ */
@@ queryFn: async (): Promise<number> => {
-      const typedData = data as CoingeckoResponse;
-
-      if (typeof typedData.torus.usd !== "number") {
-        throw new Error("Invalid response format from Coingecko API");
-      }
-
-      return typedData.torus.usd;
+      const usd = (data as unknown as Partial<CoingeckoResponse>)?.torus?.usd;
+      if (typeof usd !== "number" || !Number.isFinite(usd)) {
+        throw new Error("Invalid response format from Coingecko API");
+      }
+      return usd;
@@ return useQuery<number, Error>({
-    retry: 1,
+    staleTime: 60_000,
+    refetchOnWindowFocus: false,
+    retry: 1,
packages/ui/src/components/apr/hooks.ts (1)

60-65: APR currently truncates to integer; return precise decimals.

APR is floor-rounded to an integer before Number conversion, yielding “5.00%” instead of “5.37%”.

-  const aprBigInt = (yearlyRewards * 100n) / stakingAmount;
-  return Number(aprBigInt);
+  // Compute in basis points (×100) for 2‑decimal precision
+  const aprBp = (yearlyRewards * 10000n) / stakingAmount; // basis points
+  return Number(aprBp) / 100; // 2 decimal places
♻️ Duplicate comments (2)
apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx (2)

57-65: Add an enabled guard to prevent premature balance queries.

The useReadContract hook executes before connectionState.evmWallet.address exists, causing InvalidArgumentsError from viem when balanceOf is called with no arguments. While the args are conditionally set to undefined, the query still attempts to run, leading to unnecessary error states during wallet connection.

This issue was previously flagged but remains unaddressed.

Apply this diff to add the enabled guard:

+  const evmAddress = connectionState.evmWallet.address as
+    | `0x${string}`
+    | undefined;
+
   const { data: baseBalance } = useReadContract({
     chainId: chainIds.base,
     address: baseTorusAddress,
     abi: erc20Abi,
     functionName: "balanceOf",
-    args: connectionState.evmWallet.address
-      ? [connectionState.evmWallet.address as `0x${string}`]
-      : undefined,
+    args: evmAddress ? [evmAddress] : undefined,
+    query: {
+      enabled: Boolean(evmAddress),
+    },
   });

78-103: Guard against negative maxAmount when balance is below reserve.

When a wallet holds less than the reserved gas amount (0.01 base TORUS at line 81 or 1 native TORUS at line 91), subtracting the reserve produces a negative maxAmount. This causes fraction buttons to write negative numbers into the amount field, blocking low-balance users from using the shortcuts.

This issue was previously flagged but remains unaddressed.

Apply this diff to add guards:

   const handleFractionClick = useCallback(
     (fraction: number) => {
       if (direction === "base-to-native" && baseBalance) {
-        const maxAmount = baseBalance - BigInt(1e16); // Reserve 0.01 tokens for gas
+        const reserve = BigInt(1e16); // Reserve 0.01 tokens for gas
+        if (baseBalance <= reserve) {
+          setAmountFrom("");
+          return;
+        }
+        const maxAmount = baseBalance - reserve;
         // Use pure BigInt arithmetic to avoid precision loss
         const fractionNumerator = BigInt(Math.floor(fraction * 1_000_000));
         const fractionAmount =
           (maxAmount * fractionNumerator) / BigInt(1_000_000);
         const fractionAmountString = (Number(fractionAmount) / 1e18).toFixed(
           18,
         );
         setAmountFrom(fractionAmountString.replace(/\.?0+$/, ""));
       } else if (direction === "native-to-base" && nativeBalance.data) {
-        const maxAmount = nativeBalance.data - BigInt(1e18); // Reserve 1 token for gas
+        const reserve = BigInt(1e18); // Reserve 1 token for gas
+        if (nativeBalance.data <= reserve) {
+          setAmountFrom("");
+          return;
+        }
+        const maxAmount = nativeBalance.data - reserve;
         // Use pure BigInt arithmetic to avoid precision loss
         const fractionNumerator = BigInt(Math.floor(fraction * 1_000_000));
         const fractionAmount =
           (maxAmount * fractionNumerator) / BigInt(1_000_000);
         const fractionAmountString = (Number(fractionAmount) / 1e18).toFixed(
           18,
         );
         setAmountFrom(fractionAmountString.replace(/\.?0+$/, ""));
       }
     },
     [direction, baseBalance, nativeBalance.data],
   );
🧹 Nitpick comments (10)
apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx (1)

286-294: Fragile balance string parsing for USD conversion.

The code parses formatted balance strings by stripping " TORUS" and "," to extract numeric values for USD conversion. This approach is fragile because:

  1. It relies on formatToken's output format remaining stable (e.g., always using commas for thousands)
  2. The parsing could fail if the format includes additional characters or changes separators
  3. Precision may be lost if the formatted string is truncated or rounded

Since the raw BigInt values (baseBalance, nativeBalance.data) are already available, use them directly instead of parsing the formatted string.

Apply this diff to use raw BigInt values:

                  <div>
                    Balance:{" "}
                    <span className="font-bold">
                      {fromChain.balance}
                      {usdPrice && (
                        <span className="text-muted-foreground ml-1 font-normal">
                          (
                          <TorusToUSD
-                            torusAmount={parseFloat(
-                              fromChain.balance
-                                .replace(" TORUS", "")
-                                .replace(",", ""),
-                            )}
+                            torusAmount={
+                              direction === "base-to-native"
+                                ? baseBalance ?? 0n
+                                : nativeBalance.data ?? 0n
+                            }
                            usdPrice={usdPrice}
                            decimals={2}
                          />
                          )
                        </span>
                      )}
                    </span>
                  </div>

Apply a similar change for the toChain balance (lines 334-342).

Also applies to: 334-342

apps/torus-bridge/src/app/layout.tsx (1)

44-47: Fixed APR bar may overlap header/content; consider spacing/consistency.

The fixed, full-width bar (z-50) can overlay header controls on small screens. Align placement with the wallet app (which renders APRBarClient inline) or add a reserved top offset/padding where needed.

packages/ui/src/components/apr/torus-to-usd.tsx (1)

13-44: Improve formatting and bigint handling; add JSDoc.

  • Use Intl.NumberFormat for locale-aware currency.
  • Avoid early Number() on bigint. Prefer formatting via string or controlled precision.
  • Document props per project JSDoc conventions.

Apply this diff:

 export function TorusToUSD({
   torusAmount,
   usdPrice,
   showSymbol = true,
   decimals = 2,
   className = "",
 }: TorusToUSDProps) {
-  const torusNum = useMemo(() => {
-    if (typeof torusAmount === "bigint") {
-      return Number(torusAmount) / 1e18;
-    }
-    if (typeof torusAmount === "string") {
-      return parseFloat(torusAmount);
-    }
-    return torusAmount;
-  }, [torusAmount]);
-
-  const usdValue = useMemo(
-    () => torusNum * (usdPrice ?? 0),
-    [torusNum, usdPrice],
-  );
+  const torusNum = useMemo(() => {
+    if (typeof torusAmount === "bigint") {
+      // Convert to token units with 18 decimals; precision loss acceptable for display
+      return Number(torusAmount) / 1e18;
+    }
+    if (typeof torusAmount === "string") {
+      return parseFloat(torusAmount);
+    }
+    return torusAmount;
+  }, [torusAmount]);
+
+  const usdValue = useMemo(() => torusNum * (usdPrice ?? 0), [torusNum, usdPrice]);
+
+  const formatter = useMemo(
+    () =>
+      new Intl.NumberFormat(undefined, {
+        style: showSymbol ? "currency" : "decimal",
+        currency: "USD",
+        minimumFractionDigits: decimals,
+        maximumFractionDigits: decimals,
+      }),
+    [decimals, showSymbol],
+  );
 
   return (
     <span className={className}>
-      {showSymbol && "$"}
-      {usdValue.toFixed(decimals)}
-      {" USD"}
+      {formatter.format(usdValue)}
+      {!showSymbol && " USD"}
     </span>
   );
 }

Optional: accept a tokenDecimals prop (default 18) instead of hardcoding 1e18.

apps/torus-wallet/src/app/layout.tsx (1)

76-76: Placement differs from Bridge app; confirm intended UX.

Here APRBarClient is inline (not fixed). Bridge uses a fixed top-right overlay. Do we want consistent placement across apps? If fixed is desired, wrap similarly; if inline, ensure header/content spacing is correct.

packages/ui/tailwind.config.ts (1)

84-102: Prevent fade-up snap-back; add fill-mode.

Without a fill-mode, fade-up can revert to the initial style after finishing. Add "both" to preserve the end state.

       animation: {
-        "fade-up": "fade-up 0.8s ease-out",
+        "fade-up": "fade-up 0.8s ease-out both",
       },

Also consider using motion-safe/motion-reduce variants where applied to respect user preferences.

packages/ui/src/components/apr/apr-bar-client.tsx (1)

15-21: Tolerate missing provider and aggregate loading state.

  • Calling useTorus unconditionally will throw when used outside TorusProvider, even if api prop is provided.
  • Aggregate APR and price loading to avoid partial skeleton flicker.
-  const { api: contextApi } = useTorus();
-  const finalApi = api !== undefined ? api : contextApi;
+  // Allow usage without TorusProvider when `api` is passed
+  let contextApi: ApiPromise | null = null;
+  try {
+    ({ api: contextApi } = useTorus());
+  } catch {
+    // ignore: provider not present
+  }
+  const finalApi = api !== undefined ? api : contextApi;

-  const { apr, isLoading, totalStake, totalIssuance } = useAPR(finalApi);
-  const { data: usdPrice } = useGetTorusPrice();
+  const {
+    apr,
+    isLoading: aprLoading,
+    totalStake,
+    totalIssuance,
+  } = useAPR(finalApi);
+  const { data: usdPrice, isPending: isPriceLoading } = useGetTorusPrice();
+  const isLoading = aprLoading || isPriceLoading;

   const rewardIntervalProgress = useRewardIntervalProgress(finalApi);

   return (
     <APRBar
       apr={apr ?? undefined}
       usdPrice={usdPrice}
       totalStake={totalStake}
       totalIssuance={totalIssuance}
-      isLoading={isLoading}
+      isLoading={isLoading}
       rewardIntervalProgress={rewardIntervalProgress}
     />

Please verify APRBar’s skeleton behavior benefits from the combined loading state.

Also applies to: 24-29

packages/ui/src/components/apr/hooks.ts (3)

45-51: Avoid Number cast for halving; keep it BigInt end‑to‑end.

Casting BigInt -> Number -> BigInt risks overflow later. Compute halvingCount as BigInt and use it directly.

-  const halvingCount = Number(totalSupply / HALVING_INTERVAL);
+  const halvingCount = totalSupply / HALVING_INTERVAL;
   // Calculate emission with recycling
-  const currentEmission = calculateEmissionRate(halvingCount, recyclingRate);
+  const currentEmission = calculateEmissionRate(halvingCount, recyclingRate);
@@
-function calculateEmissionRate(
-  halvingCount: number,
-  recyclingRate: number,
-): bigint {
-  const baseEmission = BLOCK_EMISSION >> BigInt(halvingCount);
+function calculateEmissionRate(
+  halvingCount: bigint,
+  recyclingRate: number,
+): bigint {
+  const baseEmission = BLOCK_EMISSION >> halvingCount;

Also applies to: 70-77


60-62: Stake=0 fallback likely misleads APR; prefer null.

Using totalFreeBalance when totalStake is 0 skews APR instead of signaling “no APR”.

-  const stakingAmount = totalStake === 0n ? totalFreeBalance : totalStake;
-  if (stakingAmount === 0n) return null;
+  if (totalStake === 0n) return null; // no stake -> undefined APR
+  const stakingAmount = totalStake;

Confirm intended product behavior. If fallback is required, document rationale in a comment.


74-77: Optional: increase recycling precision to reduce quantization.

Scaling by 100 drops to 2‑decimal precision on recyclingRate. Consider 1e4 or 1e6 to improve accuracy.

-  return (baseEmission * BigInt(Math.floor(notRecycled * 100))) / 100n;
+  const SCALE = 10_000n;
+  return (baseEmission * BigInt(Math.floor(notRecycled * Number(SCALE)))) / SCALE;
packages/ui/src/components/apr-bar/apr-bar.tsx (1)

43-46: Keep APR fallback formatting consistent.

Show “0.00%” instead of “0%” when apr is undefined.

-        value: `${apr?.toFixed(2) ?? 0}%`,
+        value: `${(apr ?? 0).toFixed(2)}%`,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6d7aa82 and 0571173.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • apps/torus-bridge/src/app/_components/wallet-conflict-guard.tsx (0 hunks)
  • apps/torus-bridge/src/app/layout.tsx (2 hunks)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx (1 hunks)
  • apps/torus-wallet/next-env.d.ts (1 hunks)
  • apps/torus-wallet/src/app/layout.tsx (2 hunks)
  • apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx (2 hunks)
  • apps/torus-wallet/tailwind.config.ts (1 hunks)
  • packages/query-provider/src/hooks.ts (1 hunks)
  • packages/ui/package.json (2 hunks)
  • packages/ui/src/components/apr-bar/apr-bar-base.tsx (1 hunks)
  • packages/ui/src/components/apr-bar/apr-bar.tsx (2 hunks)
  • packages/ui/src/components/apr-bar/index.ts (1 hunks)
  • packages/ui/src/components/apr/apr-bar-client.tsx (1 hunks)
  • packages/ui/src/components/apr/hooks.ts (2 hunks)
  • packages/ui/src/components/apr/index.ts (1 hunks)
  • packages/ui/src/components/apr/torus-to-usd.tsx (1 hunks)
  • packages/ui/src/components/apr/use-reward-interval.ts (1 hunks)
  • packages/ui/tailwind.config.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/torus-bridge/src/app/_components/wallet-conflict-guard.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/ui/src/components/apr-bar/apr-bar-base.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • packages/query-provider/src/hooks.ts
  • packages/ui/src/components/apr/index.ts
  • packages/ui/src/components/apr-bar/index.ts
  • apps/torus-wallet/next-env.d.ts
  • packages/ui/tailwind.config.ts
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx
  • apps/torus-wallet/tailwind.config.ts
  • packages/ui/src/components/apr-bar/apr-bar.tsx
  • packages/ui/src/components/apr/use-reward-interval.ts
  • packages/ui/src/components/apr/apr-bar-client.tsx
  • packages/ui/src/components/apr/torus-to-usd.tsx
  • apps/torus-bridge/src/app/layout.tsx
  • packages/ui/src/components/apr/hooks.ts
  • apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx
  • apps/torus-wallet/src/app/layout.tsx
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • packages/query-provider/src/hooks.ts
  • packages/ui/src/components/apr/index.ts
  • packages/ui/src/components/apr-bar/index.ts
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx
  • packages/ui/src/components/apr-bar/apr-bar.tsx
  • packages/ui/src/components/apr/use-reward-interval.ts
  • packages/ui/src/components/apr/apr-bar-client.tsx
  • packages/ui/src/components/apr/torus-to-usd.tsx
  • apps/torus-bridge/src/app/layout.tsx
  • packages/ui/src/components/apr/hooks.ts
  • apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx
  • apps/torus-wallet/src/app/layout.tsx
🧠 Learnings (2)
📚 Learning: 2025-10-06T13:38:23.669Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: apps/torus-prophet-finder/AGENTS.md:0-0
Timestamp: 2025-10-06T13:38:23.669Z
Learning: Applies to apps/torus-prophet-finder/src/**/*.{ts,tsx} : Use the TypeScript path alias ~/* to import from src/* instead of deep relative paths

Applied to files:

  • packages/ui/package.json
📚 Learning: 2025-10-06T13:38:23.669Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: apps/torus-prophet-finder/AGENTS.md:0-0
Timestamp: 2025-10-06T13:38:23.669Z
Learning: Applies to apps/torus-prophet-finder/src/**/*.{ts,tsx} : Prefer ~/* alias imports over deep relative paths

Applied to files:

  • packages/ui/package.json
🧬 Code graph analysis (8)
apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx (8)
apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-dual-wallet.ts (1)
  • useDualWallet (14-143)
apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-orchestrated-transfer.ts (1)
  • useOrchestratedTransfer (35-534)
packages/torus-provider/src/torus-provider.tsx (1)
  • useTorus (266-272)
packages/query-provider/src/hooks.ts (2)
  • useFreeBalance (132-143)
  • useGetTorusPrice (464-505)
apps/torus-bridge/src/config.ts (1)
  • contractAddresses (39-48)
apps/torus-bridge/src/app/simple/_components/simple-bridge-dual-wallet-connector.tsx (1)
  • DualWalletConnector (18-155)
packages/ui/src/components/apr/torus-to-usd.tsx (1)
  • TorusToUSD (13-44)
apps/torus-bridge/src/app/simple/_components/simple-bridge-fraction-buttons.tsx (1)
  • FractionButtons (41-83)
packages/ui/src/components/apr/use-reward-interval.ts (1)
packages/query-provider/src/hooks.ts (2)
  • useLastBlock (106-116)
  • useRewardInterval (275-283)
packages/ui/src/components/apr/apr-bar-client.tsx (5)
packages/torus-provider/src/torus-provider.tsx (1)
  • useTorus (266-272)
packages/ui/src/components/apr/hooks.ts (1)
  • useAPR (117-215)
packages/query-provider/src/hooks.ts (1)
  • useGetTorusPrice (464-505)
packages/ui/src/components/apr/use-reward-interval.ts (1)
  • useRewardIntervalProgress (11-68)
packages/ui/src/components/apr-bar/apr-bar.tsx (1)
  • APRBar (26-105)
packages/ui/src/components/apr/torus-to-usd.tsx (2)
packages/db/src/schema.ts (1)
  • bigint (30-30)
packages/ui/src/components/apr/index.ts (1)
  • TorusToUSD (2-2)
apps/torus-bridge/src/app/layout.tsx (3)
apps/torus-bridge/src/context/app-context-provider.tsx (1)
  • AppContextProvider (18-50)
packages/ui/src/components/apr/apr-bar-client.tsx (1)
  • APRBarClient (14-32)
packages/ui/src/components/apr/index.ts (1)
  • APRBarClient (1-1)
packages/ui/src/components/apr/hooks.ts (3)
packages/torus-sdk-ts/src/chain/common/types.ts (1)
  • Api (8-8)
packages/torus-utils/src/typing.ts (1)
  • Nullish (8-8)
packages/query-provider/src/hooks.ts (5)
  • useTotalStake (265-273)
  • useTotalIssuance (120-128)
  • useRecyclingPercentage (301-309)
  • useTreasuryEmissionFee (155-163)
  • useIncentivesRatio (311-319)
apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx (2)
packages/torus-provider/src/torus-provider.tsx (1)
  • useTorus (266-272)
packages/ui/src/components/apr/hooks.ts (1)
  • useAPR (117-215)
apps/torus-wallet/src/app/layout.tsx (1)
packages/ui/src/components/apr/apr-bar-client.tsx (1)
  • APRBarClient (14-32)
🪛 GitHub Actions: CI
packages/ui/package.json

[error] 1-1: Lockfile specifiers do not match package.json specifications (e.g., workspace:* or catalog: entries). This causes pnpm to fail with exit code 1.

🔇 Additional comments (5)
apps/torus-wallet/tailwind.config.ts (1)

3-3: baseConfig covers all prior Tailwind customizations. The imported config includes the previous safelist, theme extensions (fonts, colors, border radii, animations, keyframes) and plugins (animate, animated, scrollbar/word-break utilities), so no additional local Tailwind config is needed.

packages/ui/src/components/apr/use-reward-interval.ts (1)

11-13: ApiPromise is compatible with Api Verified that Api is defined as ApiDecoration<"promise"> | ApiPromise, so passing ApiPromise satisfies the hook's expected Api type.

packages/ui/src/components/apr-bar/index.ts (1)

1-3: Barrel exports look good.

Consolidated exports improve DX. No issues.

packages/ui/src/components/apr/index.ts (1)

1-4: APR public surface looks coherent.

Barrel cleanly exposes client, hook, and types. No issues.

apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx (1)

33-37: APR hook integration looks correct.

Using api from useTorus and preserving apr fallback is appropriate. No issues.

if (!amountFrom) return false;
if (parseFloat(amountFrom) <= 0) return false;

const amountBigInt = BigInt(Math.floor(parseFloat(amountFrom) * 1e18));
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

Precision loss when converting user input to BigInt.

Converting the user's decimal input through parseFloat(amountFrom) * 1e18 and then Math.floor() loses precision for very small amounts (e.g., "0.000000000000000001" representing 1 wei) or amounts with more than ~15 significant digits. JavaScript's Number type has only 53 bits of precision, so amounts exceeding ~9,007 tokens with 18 decimals may be truncated.

Additionally, this conversion pattern is duplicated across three locations (lines 176, 197, 222), creating a maintenance burden.

Extract a helper function that uses string-based BigInt conversion:

/**
 * Converts a decimal string to BigInt with 18 decimals precision.
 * @param amountStr - Decimal string (e.g., "1.5" or "0.000000000000000001")
 * @returns BigInt representation with 18 decimals
 */
function parseDecimalToBigInt(amountStr: string): bigint {
  const [whole = "0", fraction = ""] = amountStr.split(".");
  const paddedFraction = fraction.padEnd(18, "0").slice(0, 18);
  return BigInt(whole + paddedFraction);
}

Then replace the three occurrences:

-    const amountBigInt = BigInt(Math.floor(parseFloat(amountFrom) * 1e18));
+    const amountBigInt = parseDecimalToBigInt(amountFrom);

Also applies to: 197-197, 222-222

🤖 Prompt for AI Agents
In apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx around
lines 176, 197 and 222, the current conversion using parseFloat(...) * 1e18 and
Math.floor loses precision for very small or high-precision amounts and is
duplicated; implement a single helper function parseDecimalToBigInt(amountStr:
string): bigint that splits on the decimal point, pads/truncates the fraction to
18 digits and returns BigInt(whole + paddedFraction), then replace the three
occurrences with calls to this helper (pass the raw input string, trim it, and
validate emptiness/invalid input beforehand); ensure the helper lives near the
top of the module or in a shared util and add minimal input validation (empty =>
0, optional trimming of leading +, and reject or handle non-numeric chars).

@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />
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

🧩 Analysis chain

Verify that this reference persists through Next.js tooling updates.

The triple-slash reference to route types is correct and enables type-safe routing. However, the comment on line 5 indicates this file is auto-generated and should not be manually edited. Recent versions of Next.js may automatically add this reference when route types are enabled.

Run the following script to check if this reference is auto-generated by Next.js:


🏁 Script executed:

#!/bin/bash
# Description: Verify if Next.js auto-generates the routes.d.ts reference

# Check Next.js version and configuration for typed routes
cat package.json | jq '.dependencies.next, .devDependencies.next'

# Check if experimental.typedRoutes is enabled in next.config
fd -t f 'next.config' -x cat {} \; | grep -i "typedRoutes\|typed.*route"

# Check if the routes.d.ts file exists
ls -la .next/types/routes.d.ts 2>&1 || echo "routes.d.ts not found"

Length of output: 102


🏁 Script executed:

#!/bin/bash
# Check Next.js version in the torus-wallet app
jq '.dependencies.next, .devDependencies.next' apps/torus-wallet/package.json

# List next.config.* files in apps/torus-wallet
ls apps/torus-wallet/next.config.* 2>/dev/null || echo "No next.config file found"

# Search for typedRoutes in any next.config under apps/torus-wallet
grep -R --include='next.config.*' -n "typedRoutes" apps/torus-wallet || echo "typedRoutes not configured"

Length of output: 76


Don’t manually edit next-env.d.ts: this file is auto-generated, and the added /// <reference path="./.next/types/routes.d.ts" /> points to a non-existent file and will be overwritten. Revert this change, add "next" to apps/torus-wallet/package.json, and enable experimental.typedRoutes in apps/torus-wallet/next.config.mjs to generate route types.

🤖 Prompt for AI Agents
In apps/torus-wallet/next-env.d.ts around line 3, remove the manual reference
addition (/// <reference path="./.next/types/routes.d.ts" />) because this
auto-generated file should not be edited; instead revert the change, add "next"
to apps/torus-wallet/package.json dependencies/devDependencies, and enable
experimental.typedRoutes in apps/torus-wallet/next.config.mjs so Next.js will
regenerate route types into .next/types automatically; do not commit manual
edits to next-env.d.ts.

Comment on lines +15 to +17
"@torus-network/torus-utils": "workspace:*",
"@torus-ts/query-provider": "workspace:*",
"@torus-network/sdk": "workspace:*",
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

Sync lockfile with new workspace specifiers.

CI is failing (Lockfile specifiers do not match package.json specifications) because the lockfile still carries the previous versions for these packages while package.json now uses workspace:*. Please re-run pnpm install (or otherwise regenerate pnpm-lock.yaml) so the lockfile reflects the new specifiers and CI can succeed.

🤖 Prompt for AI Agents
In packages/ui/package.json around lines 15 to 17, the lockfile still contains
previous version specifiers while package.json now uses workspace:* for
@torus-network/torus-utils, @torus-ts/query-provider, and @torus-network/sdk;
re-run pnpm install (or pnpm install --lockfile-only) at repo root to regenerate
pnpm-lock.yaml so the lockfile specifiers match package.json, then commit the
updated pnpm-lock.yaml to resolve the CI failure.


export function useRewardIntervalProgress() {
const { lastBlock, rewardInterval } = useWallet();
export function useRewardIntervalProgress(api: ApiPromise | null) {
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

Add JSDoc documentation for the exported hook.

Per the coding guidelines, all code should follow JSDoc conventions. This exported hook should have documentation describing its purpose, parameters, and return value.

Add JSDoc documentation:

+/**
+ * Tracks the progress of the reward interval based on the current block and reward interval.
+ * 
+ * @param api - The Polkadot API instance used to query block and reward interval data, or null if not available
+ * @returns An object containing formatted minutes, seconds, and full time string until next reward
+ */
 export function useRewardIntervalProgress(api: ApiPromise | null) {

Based on coding guidelines.

📝 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
export function useRewardIntervalProgress(api: ApiPromise | null) {
/**
* Tracks the progress of the reward interval based on the current block and reward interval.
*
* @param api - The Polkadot API instance used to query block and reward interval data, or null if not available
* @returns An object containing formatted minutes, seconds, and full time string until next reward
*/
export function useRewardIntervalProgress(api: ApiPromise | null) {
🤖 Prompt for AI Agents
packages/ui/src/components/apr/use-reward-interval.ts around line 11: the
exported hook useRewardIntervalProgress lacks JSDoc; add a JSDoc block
immediately above the export that briefly describes the hook’s purpose, lists
the parameter (api: ApiPromise | null) with its type and meaning, and documents
the return value (what is returned and its shape/meaning); keep descriptions
concise and follow existing project JSDoc style (summary sentence, @param and
@returns tags).

- Removed the `@torus-ts/torus-provider` entry from `pnpm-lock.yaml`, streamlining dependency management.
- This change enhances the overall maintainability of the project by eliminating unnecessary dependencies.
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: 5

Caution

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

⚠️ Outside diff range comments (1)
packages/query-provider/src/hooks.ts (1)

461-505: Add caching to avoid Coingecko rate limits and focus refetch storms.

Without staleTime and refetchOnWindowFocus: false, this will refetch on every window focus and often; risky for public API limits.

Apply this diff:

 export function useGetTorusPrice(
   options?: Omit<QueryObserverOptions<number, Error>, "queryKey" | "queryFn">,
 ): UseQueryResult<number, Error> {
   return useQuery<number, Error>({
     queryKey: ["torus-price"],
@@
-    retry: 1,
+    retry: 1,
+    staleTime: 5 * 60_000, // cache for 5 minutes
+    refetchOnWindowFocus: false,
     ...options,
   });
 }
♻️ Duplicate comments (2)
apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx (2)

57-65: Add enabled guard to prevent query errors before wallet connection.

The ERC20 balance query executes before the EVM wallet address exists, causing InvalidArgumentsError retries even though nothing is wrong. Add an enabled guard so the query only fires once an address is present.

Apply this diff:

+  const evmAddress = connectionState.evmWallet.address as
+    | `0x${string}`
+    | undefined;
+
   const { data: baseBalance } = useReadContract({
     chainId: chainIds.base,
     address: baseTorusAddress,
     abi: erc20Abi,
     functionName: "balanceOf",
-    args: connectionState.evmWallet.address
-      ? [connectionState.evmWallet.address as `0x${string}`]
-      : undefined,
+    args: evmAddress ? [evmAddress] : undefined,
+    query: {
+      enabled: Boolean(evmAddress),
+    },
   });

78-103: Guard against negative amounts when balance is below reserve.

If the wallet holds less than the reserved gas amount (0.01 base TORUS at line 81 or 1 native TORUS at line 91), maxAmount becomes negative. Pressing any fraction button then writes a negative number into the amount field, disabling submission with "Invalid Amount" and blocking low-balance users from using the shortcuts.

Apply this diff to both branches:

   const handleFractionClick = useCallback(
     (fraction: number) => {
       if (direction === "base-to-native" && baseBalance) {
-        const maxAmount = baseBalance - BigInt(1e16); // Reserve 0.01 tokens for gas
+        const reserve = BigInt(1e16); // Reserve 0.01 tokens for gas
+        if (baseBalance <= reserve) {
+          setAmountFrom("");
+          return;
+        }
+        const maxAmount = baseBalance - reserve;
         // Use pure BigInt arithmetic to avoid precision loss
         const fractionNumerator = BigInt(Math.floor(fraction * 1_000_000));
         const fractionAmount =
           (maxAmount * fractionNumerator) / BigInt(1_000_000);
         const fractionAmountString = (Number(fractionAmount) / 1e18).toFixed(
           18,
         );
         setAmountFrom(fractionAmountString.replace(/\.?0+$/, ""));
       } else if (direction === "native-to-base" && nativeBalance.data) {
-        const maxAmount = nativeBalance.data - BigInt(1e18); // Reserve 1 token for gas
+        const reserve = BigInt(1e18); // Reserve 1 token for gas
+        if (nativeBalance.data <= reserve) {
+          setAmountFrom("");
+          return;
+        }
+        const maxAmount = nativeBalance.data - reserve;
         // Use pure BigInt arithmetic to avoid precision loss
         const fractionNumerator = BigInt(Math.floor(fraction * 1_000_000));
         const fractionAmount =
           (maxAmount * fractionNumerator) / BigInt(1_000_000);
         const fractionAmountString = (Number(fractionAmount) / 1e18).toFixed(
           18,
         );
         setAmountFrom(fractionAmountString.replace(/\.?0+$/, ""));
       }
     },
     [direction, baseBalance, nativeBalance.data],
   );
🧹 Nitpick comments (7)
packages/ui/tailwind.config.ts (1)

84-102: Consider adding prefers-reduced-motion support for accessibility.

The new animations don't respect user motion preferences. Users with vestibular disorders or motion sensitivity may experience discomfort from continuous animations.

Consider wrapping animations in a media query or using Tailwind's motion utilities. For example, add a plugin to disable animations when motion is reduced:

plugin(function ({ addBase }) {
  addBase({
    "@media (prefers-reduced-motion: reduce)": {
      "*": {
        "animation-duration": "0.01ms !important",
        "animation-iteration-count": "1 !important",
        "transition-duration": "0.01ms !important",
      },
    },
  });
}),

Alternatively, use Tailwind's motion modifiers when applying these animations (e.g., motion-safe:animate-slide).

apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx (2)

219-235: Extract duplicate validation logic into a shared helper.

The balance-checking logic at lines 219-235 duplicates the validation in isFormValid (lines 171-189). This violates DRY and increases maintenance burden.

Refactor by extracting the shared logic:

+  const checkBalanceSufficiency = useCallback((amount: string) => {
+    if (!amount || parseFloat(amount) <= 0) return { sufficient: true, hasBalance: true };
+
+    const amountBigInt = BigInt(Math.floor(parseFloat(amount) * 1e18));
+
+    if (direction === "base-to-native") {
+      if (!baseBalance) return { sufficient: false, hasBalance: false };
+      const requiredBalance = amountBigInt + BigInt(1e16);
+      return { sufficient: baseBalance >= requiredBalance, hasBalance: true };
+    }
+
+    if (!nativeBalance.data) return { sufficient: false, hasBalance: false };
+    const requiredBalance = amountBigInt + BigInt(1e18);
+    return { sufficient: nativeBalance.data >= requiredBalance, hasBalance: true };
+  }, [direction, baseBalance, nativeBalance.data]);
+
   const isFormValid = useMemo(() => {
     if (!walletsReady) return false;
-    if (!amountFrom) return false;
-    if (parseFloat(amountFrom) <= 0) return false;
-
-    const amountBigInt = BigInt(Math.floor(parseFloat(amountFrom) * 1e18));
-
-    // Validate sufficient balance for Base to Native
-    if (direction === "base-to-native") {
-      if (!baseBalance) return false;
-      const requiredBalance = amountBigInt + BigInt(1e16); // 0.01 tokens for gas
-      return baseBalance >= requiredBalance;
-    }
-
-    // Validate sufficient balance for Native to Base
-    if (!nativeBalance.data) return false;
-    const requiredBalance = amountBigInt + BigInt(1e18); // 1 token for gas
-    return nativeBalance.data >= requiredBalance;
-  }, [walletsReady, amountFrom, direction, baseBalance, nativeBalance.data]);
+    const { sufficient } = checkBalanceSufficiency(amountFrom);
+    return sufficient;
+  }, [walletsReady, amountFrom, checkBalanceSufficiency]);
 
   const getButtonText = () => {
     if (isTransferInProgress) return "Processing...";
     if (!walletsReady) return "Connect Wallets";
     if (!amountFrom) return "Enter Amount";
     if (parseFloat(amountFrom) <= 0) return "Invalid Amount";
 
-    const amountBigInt = BigInt(Math.floor(parseFloat(amountFrom) * 1e18));
-
-    // Check for insufficient balance - Base to Native
-    if (direction === "base-to-native") {
-      if (!baseBalance) return "Loading Balance...";
-      const requiredBalance = amountBigInt + BigInt(1e16);
-      if (baseBalance < requiredBalance) return "Insufficient Balance";
-    }
-
-    // Check for insufficient balance - Native to Base
-    if (direction === "native-to-base") {
-      if (!nativeBalance.data) return "Loading Balance...";
-      const requiredBalance = amountBigInt + BigInt(1e18);
-      if (nativeBalance.data < requiredBalance) return "Insufficient Balance";
-    }
+    const { sufficient, hasBalance } = checkBalanceSufficiency(amountFrom);
+    if (!hasBalance) return "Loading Balance...";
+    if (!sufficient) return "Insufficient Balance";
 
     // Return specific transaction description based on direction
     return direction === "base-to-native"
       ? "Submit Base to Torus Transaction"
       : "Submit Torus to Base Transaction";
   };
 
   const hasInsufficientBalance = () => {
-    if (!amountFrom || parseFloat(amountFrom) <= 0) return false;
-
-    const amountBigInt = BigInt(Math.floor(parseFloat(amountFrom) * 1e18));
-
-    // Check for insufficient balance - Base to Native
-    if (direction === "base-to-native") {
-      if (!baseBalance) return false;
-      const requiredBalance = amountBigInt + BigInt(1e16);
-      return baseBalance < requiredBalance;
-    }
-
-    // Check for insufficient balance - Native to Base
-    if (!nativeBalance.data) return false;
-    const requiredBalance = amountBigInt + BigInt(1e18);
-    return nativeBalance.data < requiredBalance;
+    const { sufficient, hasBalance } = checkBalanceSufficiency(amountFrom);
+    return hasBalance && !sufficient;
   };

176-176: Consider safer string-to-BigInt conversion to avoid precision loss.

Converting amountFrom via Math.floor(parseFloat(amountFrom) * 1e18) can lose precision for large numbers (exceeding JavaScript's safe integer range) or amounts with many decimal places. While unlikely in typical bridge scenarios, a safer approach parses the string directly into BigInt components.

Consider extracting a helper that splits the decimal string and constructs the BigInt without intermediate floating-point conversion:

const parseTokenAmount = (amount: string): bigint => {
  const [whole, fraction = ""] = amount.split(".");
  const paddedFraction = fraction.padEnd(18, "0").slice(0, 18);
  return BigInt(whole + paddedFraction);
};

Then replace the conversions:

-    const amountBigInt = BigInt(Math.floor(parseFloat(amountFrom) * 1e18));
+    const amountBigInt = parseTokenAmount(amountFrom);

This ensures exact representation for all valid decimal inputs.

Also applies to: 197-197, 222-222

packages/ui/src/components/apr/use-reward-interval.ts (1)

26-47: Avoid nested setState inside updater to prevent flicker and redundant renders.

Return the recomputed seconds directly from the updater; don’t call setTimeLeft inside it.

Apply this diff:

-    const calculateTimeLeft = () => {
-      const interval = Number(rewardInterval.data);
-      const currentBlock = Number(lastBlock.data.blockNumber);
-      const blocksIntoInterval = currentBlock % interval;
-      const blocksUntilNext = interval - blocksIntoInterval;
-      const secondsUntilNext =
-        blocksUntilNext * CONSTANTS.TIME.BLOCK_TIME_SECONDS;
-
-      setTimeLeft(secondsUntilNext);
-    };
+    const calculateTimeLeft = (): number => {
+      const interval = Number(rewardInterval.data);
+      const currentBlock = Number(lastBlock.data.blockNumber);
+      const blocksIntoInterval = currentBlock % interval;
+      const blocksUntilNext = interval - blocksIntoInterval;
+      return blocksUntilNext * CONSTANTS.TIME.BLOCK_TIME_SECONDS;
+    };
@@
-    calculateTimeLeft();
+    setTimeLeft(calculateTimeLeft());
@@
-      setTimeLeft((prev) => {
-        if (prev === null || prev <= 1) {
-          calculateTimeLeft();
-          return null;
-        }
-        return prev - 1;
-      });
+      setTimeLeft((prev) => {
+        if (prev === null || prev <= 1) {
+          return calculateTimeLeft();
+        }
+        return prev - 1;
+      });
packages/ui/src/components/apr/hooks.ts (1)

70-77: Optional: unify ratio precision with 1e18 scale.

calculateEmissionRate uses a coarse x100 scale, while calculateYearlyRewards uses 1e18. Consider aligning scales to reduce quantization error.

packages/ui/src/components/apr-bar/index.ts (1)

1-3: Consider exporting APRBarProps for better type safety.

APRBarProps defined in apr-bar.tsx is used by consumers (e.g., APRBarClient) but is not exported. Exporting it would allow consumers to type-check props explicitly.

Add the type export:

 export { APRBar } from "./apr-bar";
 export { APRBarBase } from "./apr-bar-base";
 export { AppBarDataGroup, AppBarSeparator } from "./apr-bar-shared";
+export type { APRBarProps } from "./apr-bar";
packages/ui/src/components/apr/torus-to-usd.tsx (1)

25-27: Handle invalid string inputs gracefully.

parseFloat() returns NaN for invalid strings, which could propagate to the USD calculation. Consider validating the input or providing a fallback.

Add validation:

 if (typeof torusAmount === "string") {
-  return parseFloat(torusAmount);
+  const parsed = parseFloat(torusAmount);
+  return Number.isNaN(parsed) ? 0 : parsed;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6d7aa82 and 22d6244.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • apps/torus-bridge/src/app/_components/wallet-conflict-guard.tsx (0 hunks)
  • apps/torus-bridge/src/app/layout.tsx (2 hunks)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx (1 hunks)
  • apps/torus-wallet/next-env.d.ts (1 hunks)
  • apps/torus-wallet/src/app/layout.tsx (2 hunks)
  • apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx (2 hunks)
  • apps/torus-wallet/tailwind.config.ts (1 hunks)
  • packages/query-provider/src/hooks.ts (1 hunks)
  • packages/ui/package.json (2 hunks)
  • packages/ui/src/components/apr-bar/apr-bar-base.tsx (1 hunks)
  • packages/ui/src/components/apr-bar/apr-bar.tsx (2 hunks)
  • packages/ui/src/components/apr-bar/index.ts (1 hunks)
  • packages/ui/src/components/apr/apr-bar-client.tsx (1 hunks)
  • packages/ui/src/components/apr/hooks.ts (2 hunks)
  • packages/ui/src/components/apr/index.ts (1 hunks)
  • packages/ui/src/components/apr/torus-to-usd.tsx (1 hunks)
  • packages/ui/src/components/apr/use-reward-interval.ts (1 hunks)
  • packages/ui/tailwind.config.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/torus-bridge/src/app/_components/wallet-conflict-guard.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/ui/src/components/apr-bar/apr-bar-base.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • packages/ui/src/components/apr/torus-to-usd.tsx
  • packages/ui/src/components/apr-bar/index.ts
  • apps/torus-wallet/src/app/layout.tsx
  • packages/ui/src/components/apr/apr-bar-client.tsx
  • packages/ui/src/components/apr/index.ts
  • apps/torus-bridge/src/app/layout.tsx
  • packages/query-provider/src/hooks.ts
  • packages/ui/src/components/apr-bar/apr-bar.tsx
  • apps/torus-wallet/next-env.d.ts
  • packages/ui/tailwind.config.ts
  • packages/ui/src/components/apr/use-reward-interval.ts
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx
  • packages/ui/src/components/apr/hooks.ts
  • apps/torus-wallet/tailwind.config.ts
  • apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • packages/ui/src/components/apr/torus-to-usd.tsx
  • packages/ui/src/components/apr-bar/index.ts
  • apps/torus-wallet/src/app/layout.tsx
  • packages/ui/src/components/apr/apr-bar-client.tsx
  • packages/ui/src/components/apr/index.ts
  • apps/torus-bridge/src/app/layout.tsx
  • packages/query-provider/src/hooks.ts
  • packages/ui/src/components/apr-bar/apr-bar.tsx
  • packages/ui/src/components/apr/use-reward-interval.ts
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx
  • packages/ui/src/components/apr/hooks.ts
  • apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx
🧠 Learnings (3)
📚 Learning: 2025-10-06T13:38:23.669Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: apps/torus-prophet-finder/AGENTS.md:0-0
Timestamp: 2025-10-06T13:38:23.669Z
Learning: Applies to apps/torus-prophet-finder/src/**/*.{ts,tsx} : Use the TypeScript path alias ~/* to import from src/* instead of deep relative paths

Applied to files:

  • packages/ui/package.json
📚 Learning: 2025-10-06T13:38:23.669Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: apps/torus-prophet-finder/AGENTS.md:0-0
Timestamp: 2025-10-06T13:38:23.669Z
Learning: Applies to apps/torus-prophet-finder/src/**/*.{ts,tsx} : Prefer ~/* alias imports over deep relative paths

Applied to files:

  • packages/ui/package.json
📚 Learning: 2025-07-27T01:55:40.478Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: packages/torus-sdk-ts/src/chain/CLAUDE.md:0-0
Timestamp: 2025-07-27T01:55:40.478Z
Learning: Applies to packages/torus-sdk-ts/src/chain/**/*.{ts,tsx} : Use Result types (from `torus-network/torus-utils/result`) for query function return values, with specific error types instead of generic `Error` unions.

Applied to files:

  • packages/ui/src/components/apr/hooks.ts
🧬 Code graph analysis (8)
packages/ui/src/components/apr/torus-to-usd.tsx (2)
packages/db/src/schema.ts (1)
  • bigint (30-30)
packages/ui/src/components/apr/index.ts (1)
  • TorusToUSD (2-2)
apps/torus-wallet/src/app/layout.tsx (2)
packages/ui/src/components/apr/apr-bar-client.tsx (1)
  • APRBarClient (14-32)
packages/ui/src/components/apr/index.ts (1)
  • APRBarClient (1-1)
packages/ui/src/components/apr/apr-bar-client.tsx (5)
packages/torus-provider/src/torus-provider.tsx (1)
  • useTorus (266-272)
packages/ui/src/components/apr/hooks.ts (1)
  • useAPR (117-215)
packages/query-provider/src/hooks.ts (1)
  • useGetTorusPrice (464-505)
packages/ui/src/components/apr/use-reward-interval.ts (1)
  • useRewardIntervalProgress (11-68)
packages/ui/src/components/apr-bar/apr-bar.tsx (1)
  • APRBar (26-105)
apps/torus-bridge/src/app/layout.tsx (2)
apps/torus-bridge/src/context/app-context-provider.tsx (1)
  • AppContextProvider (18-50)
packages/ui/src/components/apr/apr-bar-client.tsx (1)
  • APRBarClient (14-32)
packages/ui/src/components/apr/use-reward-interval.ts (1)
packages/query-provider/src/hooks.ts (2)
  • useLastBlock (106-116)
  • useRewardInterval (275-283)
apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx (11)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (1)
  • SimpleBridgeDirection (3-3)
apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-dual-wallet.ts (1)
  • useDualWallet (14-143)
apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-orchestrated-transfer.ts (1)
  • useOrchestratedTransfer (35-534)
packages/torus-provider/src/torus-provider.tsx (1)
  • useTorus (266-272)
packages/query-provider/src/hooks.ts (2)
  • useFreeBalance (132-143)
  • useGetTorusPrice (464-505)
packages/torus-sdk-ts/src/types/address.ts (1)
  • SS58Address (37-37)
apps/torus-bridge/src/config.ts (1)
  • contractAddresses (39-48)
apps/torus-bridge/src/app/simple/_components/simple-bridge-dual-wallet-connector.tsx (1)
  • DualWalletConnector (18-155)
packages/ui/src/components/apr/torus-to-usd.tsx (1)
  • TorusToUSD (13-44)
apps/torus-bridge/src/app/simple/_components/simple-bridge-fraction-buttons.tsx (1)
  • FractionButtons (41-83)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (1)
  • TransactionLifecycleDialog (71-207)
packages/ui/src/components/apr/hooks.ts (4)
packages/ui/src/components/apr/index.ts (2)
  • APRResult (4-4)
  • useAPR (3-3)
packages/torus-sdk-ts/src/chain/common/types.ts (1)
  • Api (8-8)
packages/torus-utils/src/typing.ts (1)
  • Nullish (8-8)
packages/query-provider/src/hooks.ts (5)
  • useTotalStake (265-273)
  • useTotalIssuance (120-128)
  • useRecyclingPercentage (301-309)
  • useTreasuryEmissionFee (155-163)
  • useIncentivesRatio (311-319)
apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx (2)
packages/torus-provider/src/torus-provider.tsx (1)
  • useTorus (266-272)
packages/ui/src/components/apr/hooks.ts (1)
  • useAPR (117-215)
🪛 GitHub Actions: CI
packages/ui/package.json

[error] 1-1: Lockfile specifiers do not match package.json specs in the workspace. The lockfile must be updated to reflect the declared dependencies.

🔇 Additional comments (14)
apps/torus-wallet/tailwind.config.ts (1)

3-3: Local Tailwind overrides moved to baseConfig

The packages/ui/tailwind.config.ts base config already provides all previously defined theme extensions, animations, keyframes, and plugins, so removing the local config poses no breaking changes.

apps/torus-bridge/src/app/layout.tsx (2)

5-5: LGTM!

The import is correctly added for the new APRBarClient component.


43-48: Fixed positioning is correct as-is. Container uses only static layout, so the <div className="fixed…"> is still viewport-anchored and will appear top-right. No changes needed.

apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx (1)

33-35: APR hook integration looks good.

Using api from useTorus with the new useAPR(api) shape is correct; safe default preserved.

apps/torus-wallet/src/app/layout.tsx (1)

5-5: Swapping in APRBarClient is correct and non-breaking.

Keeps layout behavior while sourcing APR via shared UI package.

Also applies to: 76-76

packages/ui/src/components/apr/index.ts (1)

1-4: LGTM!

Clean barrel exports following standard patterns. The public API surface is well-defined with appropriate type exports.

packages/ui/src/components/apr/apr-bar-client.tsx (3)

14-16: LGTM!

The API fallback pattern correctly prioritizes the explicit api prop over the context API.


18-20: Verify error handling for failed queries.

The component doesn't check isError states from the hooks. If any query fails, the component will still render with potentially stale or missing data.

Consider how errors should be handled:

  • Should failed queries show an error state in the UI?
  • Should the component gracefully degrade with placeholder values?
  • Should errors be logged for observability?

24-24: Correct null-to-undefined conversion.

Converting null to undefined aligns with APRBar's optional apr?: number prop signature.

packages/ui/src/components/apr/torus-to-usd.tsx (2)

32-43: LGTM with a note on edge cases.

The USD calculation and rendering are correct. Note that toFixed() will throw if usdValue is NaN or Infinity, but the validation suggestion above would prevent this.


20-30: 18-decimal conversion is valid: TORUS tokens are configured with 18 decimals, so dividing by 1e18 is appropriate.

packages/ui/src/components/apr-bar/apr-bar.tsx (3)

15-24: Well-structured props interface.

APRBarProps clearly defines the component's contract. As noted in the apr-bar/index.ts review, consider exporting this type for consumer type safety.


26-33: Excellent refactoring to prop-driven design.

Moving from internal hooks to props improves testability and composability. The component is now a pure presentation layer.


66-66: Correct defensive access.

The optional chaining with nullish coalescing rewardIntervalProgress?.full ?? "00:00" safely handles undefined values.

@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />
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

Verify that manually adding this reference is the correct approach for Next.js 15.x.

The file comment at lines 5-6 states "This file should not be edited," yet a manual reference to route types is being added. In Next.js 15.x, typed routes support may auto-generate this reference or require configuration in next.config.js instead. If next-env.d.ts is regenerated during builds, this manual change could be lost.

Please search the web to confirm the proper way to enable typed routes in Next.js 15.x:


🌐 Web query:

How to enable typed routes in Next.js 15.x? Should next-env.d.ts be manually edited or is the reference auto-generated?

💡 Result:

Short answer:

  • Enable typed routes by turning on typedRoutes in next.config (experimental.typedRoutes = true) and including the generated types (".next/types/**/*.ts") in your tsconfig; run next dev / next build (or next typegen) to generate the types. [1][2]
  • Do NOT manually edit next-env.d.ts — it's auto-generated and can be overwritten. Put any custom declarations in a separate .d.ts and add that to tsconfig include. [2][3]

References:

  • Next.js typed routes / config & include instructions. [1]
  • Next.js TypeScript docs: next-env.d.ts is autogenerated; add .next/types/**/*.ts to tsconfig include. [2][3]

Remove manual reference from next-env.d.ts
next-env.d.ts is auto-generated and overwritten. Delete the added triple-slash line, enable experimental.typedRoutes = true in next.config.js, and include ".next/types/**/*.ts" in your tsconfig.json (or place custom declarations in a separate .d.ts).

🤖 Prompt for AI Agents
In apps/torus-wallet/next-env.d.ts around line 3, remove the manually added
triple-slash reference to "./.next/types/routes.d.ts" because next-env.d.ts is
auto-generated and will be overwritten; instead enable experimental.typedRoutes
= true in next.config.js and add ".next/types/**/*.ts" to the "include" array in
tsconfig.json (or move any custom declarations into a separate .d.ts file) so
typed routes are picked up without modifying next-env.d.ts.

Comment on lines +15 to +17
"@torus-network/torus-utils": "workspace:*",
"@torus-ts/query-provider": "workspace:*",
"@torus-network/sdk": "workspace:*",
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

Update the pnpm lockfile to match these new workspace deps.

CI is failing (Lockfile specifiers do not match package.json specs) because the lockfile still reflects the old dependency graph. Please run pnpm install (or the repo’s lockfile update task) and commit the updated lockfile so installs don’t break.

🤖 Prompt for AI Agents
In packages/ui/package.json around lines 15 to 17, the package.json was updated
to use workspace:* specifiers for torus packages but the pnpm lockfile was not
updated, causing CI to fail with "Lockfile specifiers do not match package.json
specs"; run pnpm install (or the repository's lockfile update script) at repo
root to regenerate/update pnpm-lock.yaml, verify the lockfile now contains the
new workspace:* specifiers, and commit the updated lockfile alongside this
package.json change.

Comment on lines 156 to 170
const isDataComplete = useMemo(
() => queries.every((query) => !!query.data),
[queries],
() =>
!!totalStakeQuery.data &&
!!totalIssuanceQuery.data &&
!!recyclingPercentageQuery.data &&
!!treasuryEmissionFeeQuery.data &&
!!incentivesRatioQuery.data,
[
totalStakeQuery.data,
totalIssuanceQuery.data,
recyclingPercentageQuery.data,
treasuryEmissionFeeQuery.data,
incentivesRatioQuery.data,
],
);
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

Fix isDataComplete: truthy checks break on 0n values.

BigInt 0n is falsy; valid zeroes incorrectly mark data incomplete, blocking APR calculation.

Apply this diff:

-  const isDataComplete = useMemo(
-    () =>
-      !!totalStakeQuery.data &&
-      !!totalIssuanceQuery.data &&
-      !!recyclingPercentageQuery.data &&
-      !!treasuryEmissionFeeQuery.data &&
-      !!incentivesRatioQuery.data,
+  const isDataComplete = useMemo(
+    () =>
+      totalStakeQuery.data != null &&
+      totalIssuanceQuery.data != null &&
+      recyclingPercentageQuery.data != null &&
+      treasuryEmissionFeeQuery.data != null &&
+      incentivesRatioQuery.data != null,
     [
       totalStakeQuery.data,
       totalIssuanceQuery.data,
       recyclingPercentageQuery.data,
       treasuryEmissionFeeQuery.data,
       incentivesRatioQuery.data,
     ],
   );
📝 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 isDataComplete = useMemo(
() => queries.every((query) => !!query.data),
[queries],
() =>
!!totalStakeQuery.data &&
!!totalIssuanceQuery.data &&
!!recyclingPercentageQuery.data &&
!!treasuryEmissionFeeQuery.data &&
!!incentivesRatioQuery.data,
[
totalStakeQuery.data,
totalIssuanceQuery.data,
recyclingPercentageQuery.data,
treasuryEmissionFeeQuery.data,
incentivesRatioQuery.data,
],
);
const isDataComplete = useMemo(
() =>
totalStakeQuery.data != null &&
totalIssuanceQuery.data != null &&
recyclingPercentageQuery.data != null &&
treasuryEmissionFeeQuery.data != null &&
incentivesRatioQuery.data != null,
[
totalStakeQuery.data,
totalIssuanceQuery.data,
recyclingPercentageQuery.data,
treasuryEmissionFeeQuery.data,
incentivesRatioQuery.data,
],
);
🤖 Prompt for AI Agents
In packages/ui/src/components/apr/hooks.ts around lines 156 to 170, the
isDataComplete check uses truthy coercion (!!value) which treats BigInt 0n as
falsy and incorrectly marks valid zero values as missing; replace each truthy
check with an explicit null/undefined guard (e.g., data !== undefined && data
!== null) for totalStakeQuery.data, totalIssuanceQuery.data,
recyclingPercentageQuery.data, treasuryEmissionFeeQuery.data, and
incentivesRatioQuery.data so zero values are considered present while still
excluding null/undefined.

@@ -1,9 +1,16 @@
"use client";

import type { ApiPromise } from "@polkadot/api";
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

Widen hook API type to match callers (APRBarClient passes Api).

Current signature only accepts ApiPromise; callers pass Api (ApiDecoration|ApiPromise). Widen to Api | Nullish and align with other hooks.

Apply this diff:

-import type { ApiPromise } from "@polkadot/api";
+import type { Api } from "@torus-network/sdk/chain";
+import type { Nullish } from "@torus-network/torus-utils/typing";
@@
-export function useRewardIntervalProgress(api: ApiPromise | null) {
-  const lastBlock = useLastBlock(api);
-  const rewardInterval = useRewardInterval(api);
+export function useRewardIntervalProgress(api: Api | Nullish) {
+  const lastBlock = useLastBlock(api);
+  const rewardInterval = useRewardInterval(api);

Also applies to: 11-14

🤖 Prompt for AI Agents
In packages/ui/src/components/apr/use-reward-interval.ts around lines 3 and
11-14, the hook's API type is too narrow (only ApiPromise) while callers pass
Api (ApiDecoration|ApiPromise) or null/undefined; widen the type to accept Api |
null | undefined (or the union of ApiDecoration and ApiPromise) and update the
import/type declarations to match the other hooks so the hook signature and any
internal null checks handle a nullish Api correctly.

Comment on lines +84 to +93
animation: {
slide: "slide 2.5s linear infinite",
marquee: "marquee 80s linear infinite",
"fade-up": "fade-up 0.8s ease-out",
},
keyframes: {
slide: {
"0%": { transform: "translateX(-500px)" },
"100%": { transform: "translateX(500px)" },
},
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

Fixed pixel values in slide animation are not responsive.

The slide animation uses fixed pixel values (-500px to 500px) which may not work well across different screen sizes. On smaller viewports or with smaller elements, the animation could move content off-screen or appear disproportionate.

Consider using percentage-based or viewport-relative units for better responsiveness:

       animation: {
-        slide: "slide 2.5s linear infinite",
+        slide: "slide 2.5s linear infinite",
         marquee: "marquee 80s linear infinite",
         "fade-up": "fade-up 0.8s ease-out",
       },
       keyframes: {
         slide: {
-          "0%": { transform: "translateX(-500px)" },
-          "100%": { transform: "translateX(500px)" },
+          "0%": { transform: "translateX(-100%)" },
+          "100%": { transform: "translateX(100%)" },
         },
📝 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
animation: {
slide: "slide 2.5s linear infinite",
marquee: "marquee 80s linear infinite",
"fade-up": "fade-up 0.8s ease-out",
},
keyframes: {
slide: {
"0%": { transform: "translateX(-500px)" },
"100%": { transform: "translateX(500px)" },
},
animation: {
slide: "slide 2.5s linear infinite",
marquee: "marquee 80s linear infinite",
"fade-up": "fade-up 0.8s ease-out",
},
keyframes: {
slide: {
"0%": { transform: "translateX(-100%)" },
"100%": { transform: "translateX(100%)" },
},
🤖 Prompt for AI Agents
In packages/ui/tailwind.config.ts around lines 84 to 93, the slide keyframes use
fixed pixel translations ("translateX(-500px)" to "translateX(500px)") which
aren’t responsive; replace those fixed pixel values with percentage or
viewport-relative units (e.g., translateX(-100%) to translateX(100%) or
translateX(-100vw) to translateX(100vw)) so the animation scales with
element/screen size, and verify the chosen units work with the element width and
surrounding layout to avoid clipping.

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: 4

♻️ Duplicate comments (2)
apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx (2)

57-65: Guard contract read until EVM address exists (avoid InvalidArgumentsError).

useReadContract is invoked with undefined args before wallet connects; Viem rejects and the query loops in error. Add an enabled guard and reuse a derived address.

Apply:

+  const evmAddress = connectionState.evmWallet.address as
+    | `0x${string}`
+    | undefined;
+
   const { data: baseBalance } = useReadContract({
     chainId: chainIds.base,
     address: baseTorusAddress,
     abi: erc20Abi,
     functionName: "balanceOf",
-    args: connectionState.evmWallet.address
-      ? [connectionState.evmWallet.address as `0x${string}`]
-      : undefined,
+    args: evmAddress ? [evmAddress] : undefined,
+    query: {
+      enabled: Boolean(evmAddress),
+    },
   });

176-189: Avoid parseFloat*1e18; use string-safe decimal→BigInt conversion.

Converting through Number loses precision; also replace numeric BigInt constants with literals.

Apply:

-    const amountBigInt = BigInt(Math.floor(parseFloat(amountFrom) * 1e18));
+    const amountBigInt = parseDecimalToBigInt(amountFrom);
@@
-      const requiredBalance = amountBigInt + BigInt(1e16); // 0.01 tokens for gas
+      const requiredBalance = amountBigInt + BASE_GAS_RESERVE; // 0.01 tokens for gas
@@
-    const requiredBalance = amountBigInt + BigInt(1e18); // 1 token for gas
+    const requiredBalance = amountBigInt + NATIVE_GAS_RESERVE; // 1 token for gas
-    const amountBigInt = BigInt(Math.floor(parseFloat(amountFrom) * 1e18));
+    const amountBigInt = parseDecimalToBigInt(amountFrom);
@@
-      const requiredBalance = amountBigInt + BigInt(1e16);
+      const requiredBalance = amountBigInt + BASE_GAS_RESERVE;
@@
-      const requiredBalance = amountBigInt + BigInt(1e18);
+      const requiredBalance = amountBigInt + NATIVE_GAS_RESERVE;
-    const amountBigInt = BigInt(Math.floor(parseFloat(amountFrom) * 1e18));
+    const amountBigInt = parseDecimalToBigInt(amountFrom);
@@
-      const requiredBalance = amountBigInt + BigInt(1e16);
+      const requiredBalance = amountBigInt + BASE_GAS_RESERVE;
@@
-    const requiredBalance = amountBigInt + BigInt(1e18);
+    const requiredBalance = amountBigInt + NATIVE_GAS_RESERVE;

Add once near the top:

function parseDecimalToBigInt(amountStr: string, decimals = 18): bigint {
  const s = (amountStr || "").trim();
  if (s === "") return 0n;
  const [whole = "0", fraction = ""] = s.split(".");
  const cleanWhole = whole.replace(/^0+(?=\d)/, "");
  const paddedFraction = fraction.padEnd(decimals, "0").slice(0, decimals);
  const numeric = (cleanWhole === "" ? "0" : cleanWhole) + paddedFraction;
  return BigInt(numeric);
}

Also applies to: 197-211, 222-235

🧹 Nitpick comments (1)
apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx (1)

191-217: Reduce duplication in balance checks and button text (optional).

getButtonText and hasInsufficientBalance duplicate parsing and reserve math. Extract a single helper that returns {loading, invalid, shortfall} to drive both UI states.

Also applies to: 219-235

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0571173 and 2bc946f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx (1 hunks)
  • apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx (2 hunks)
  • packages/ui/src/components/apr-bar/apr-bar-base.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx
  • apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx
  • packages/ui/src/components/apr-bar/apr-bar-base.tsx
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx
  • apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx
  • packages/ui/src/components/apr-bar/apr-bar-base.tsx
🧬 Code graph analysis (2)
apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx (9)
apps/torus-bridge/src/app/simple/_components/simple-bridge-types.ts (1)
  • SimpleBridgeDirection (3-3)
apps/torus-bridge/src/app/simple/hooks/use-simple-bridge-dual-wallet.ts (1)
  • useDualWallet (14-143)
packages/torus-provider/src/torus-provider.tsx (1)
  • useTorus (266-272)
packages/query-provider/src/hooks.ts (2)
  • useFreeBalance (132-143)
  • useGetTorusPrice (464-505)
apps/torus-bridge/src/config.ts (1)
  • contractAddresses (39-48)
apps/torus-bridge/src/app/simple/_components/simple-bridge-dual-wallet-connector.tsx (1)
  • DualWalletConnector (18-155)
packages/ui/src/components/apr/torus-to-usd.tsx (1)
  • TorusToUSD (13-44)
apps/torus-bridge/src/app/simple/_components/simple-bridge-fraction-buttons.tsx (1)
  • FractionButtons (41-83)
apps/torus-bridge/src/app/simple/_components/simple-bridge-transaction-lifecycle-dialog.tsx (1)
  • TransactionLifecycleDialog (71-207)
apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx (2)
packages/torus-provider/src/torus-provider.tsx (1)
  • useTorus (266-272)
packages/ui/src/components/apr/hooks.ts (1)
  • useAPR (117-215)
⏰ 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). (6)
  • GitHub Check: turbo-check / run
  • GitHub Check: check
  • GitHub Check: typecheck
  • GitHub Check: format
  • GitHub Check: lint
  • GitHub Check: Analyze (actions)
🔇 Additional comments (3)
apps/torus-wallet/src/app/staking/_components/staking-calculator/staking-calculator.tsx (2)

6-6: LGTM! APR hook import migrated correctly.

The import has been successfully migrated from a local hook to the centralized UI package, aligning with the PR objective to update APR UI to prop-driven variants.


33-34: LGTM! API parameter correctly supplied to APR hook.

The changes properly extract the api from the Torus context and pass it to the updated useAPR(api) hook. The hook signature accepts Api | Nullish, so it will gracefully handle any undefined API states by returning appropriate loading/error states. The existing fallback (apr ?? 24 on line 36) ensures the calculator remains functional even when APR data is unavailable.

packages/ui/src/components/apr-bar/apr-bar-base.tsx (1)

9-14: Custom Tailwind animations confirmed. animate-fade-up, animate-slide, and animate-marquee are defined in packages/ui/tailwind.config.ts.

Comment on lines +78 to +101
const handleFractionClick = useCallback(
(fraction: number) => {
if (direction === "base-to-native" && baseBalance) {
const maxAmount = baseBalance - BigInt(1e16); // Reserve 0.01 tokens for gas
// Use pure BigInt arithmetic to avoid precision loss
const fractionNumerator = BigInt(Math.floor(fraction * 1_000_000));
const fractionAmount =
(maxAmount * fractionNumerator) / BigInt(1_000_000);
const fractionAmountString = (Number(fractionAmount) / 1e18).toFixed(
18,
);
setAmountFrom(fractionAmountString.replace(/\.?0+$/, ""));
} else if (direction === "native-to-base" && nativeBalance.data) {
const maxAmount = nativeBalance.data - BigInt(1e18); // Reserve 1 token for gas
// Use pure BigInt arithmetic to avoid precision loss
const fractionNumerator = BigInt(Math.floor(fraction * 1_000_000));
const fractionAmount =
(maxAmount * fractionNumerator) / BigInt(1_000_000);
const fractionAmountString = (Number(fractionAmount) / 1e18).toFixed(
18,
);
setAmountFrom(fractionAmountString.replace(/\.?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

Clamp reserves and avoid precision loss in fraction calculation.

  • Prevent negative values when balance ≤ reserve.
  • Don’t use BigInt(1e16/1e18); use BigInt literals.
  • Avoid Number() when formatting BigInt amounts.

Apply:

   const handleFractionClick = useCallback(
     (fraction: number) => {
       if (direction === "base-to-native" && baseBalance) {
-        const maxAmount = baseBalance - BigInt(1e16); // Reserve 0.01 tokens for gas
-        // Use pure BigInt arithmetic to avoid precision loss
-        const fractionNumerator = BigInt(Math.floor(fraction * 1_000_000));
-        const fractionAmount =
-          (maxAmount * fractionNumerator) / BigInt(1_000_000);
-        const fractionAmountString = (Number(fractionAmount) / 1e18).toFixed(
-          18,
-        );
-        setAmountFrom(fractionAmountString.replace(/\.?0+$/, ""));
+        const reserve = BASE_GAS_RESERVE;
+        if (baseBalance <= reserve) {
+          setAmountFrom("");
+          return;
+        }
+        const maxAmount = baseBalance - reserve;
+        const fractionNumerator = BigInt(Math.floor(fraction * 1_000_000));
+        const fractionDenominator = 1_000_000n;
+        const fractionAmount = (maxAmount * fractionNumerator) / fractionDenominator;
+        setAmountFrom(formatWeiToDecimalString(fractionAmount));
       } else if (direction === "native-to-base" && nativeBalance.data) {
-        const maxAmount = nativeBalance.data - BigInt(1e18); // Reserve 1 token for gas
-        // Use pure BigInt arithmetic to avoid precision loss
-        const fractionNumerator = BigInt(Math.floor(fraction * 1_000_000));
-        const fractionAmount =
-          (maxAmount * fractionNumerator) / BigInt(1_000_000);
-        const fractionAmountString = (Number(fractionAmount) / 1e18).toFixed(
-          18,
-        );
-        setAmountFrom(fractionAmountString.replace(/\.?0+$/, ""));
+        const reserve = NATIVE_GAS_RESERVE;
+        if (nativeBalance.data <= reserve) {
+          setAmountFrom("");
+          return;
+        }
+        const maxAmount = nativeBalance.data - reserve;
+        const fractionNumerator = BigInt(Math.floor(fraction * 1_000_000));
+        const fractionDenominator = 1_000_000n;
+        const fractionAmount = (maxAmount * fractionNumerator) / fractionDenominator;
+        setAmountFrom(formatWeiToDecimalString(fractionAmount));
       }
     },
     [direction, baseBalance, nativeBalance.data],
   );

Add near the top (outside this hunk):

const BASE_GAS_RESERVE = 10n ** 16n; // 0.01 TORUS
const NATIVE_GAS_RESERVE = 10n ** 18n; // 1 TORUS

function formatWeiToDecimalString(amount: bigint, decimals = 18): string {
  const base = 10n ** BigInt(decimals);
  const whole = amount / base;
  const fraction = amount % base;
  const fractionStr = fraction
    .toString()
    .padStart(decimals, "0")
    .replace(/0+$/, "");
  return fractionStr.length ? `${whole}.${fractionStr}` : whole.toString();
}
🤖 Prompt for AI Agents
In apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx around
lines 78 to 101, replace the current fraction click logic and add top-level
helpers: define BASE_GAS_RESERVE = 10n ** 16n and NATIVE_GAS_RESERVE = 10n **
18n, and add formatWeiToDecimalString(amount: bigint, decimals = 18) that
formats a bigint wei amount to a decimal string without using Number(). In the
handler, use the BigInt reserve literals (BASE_GAS_RESERVE /
NATIVE_GAS_RESERVE), clamp maxAmount to zero when balance <= reserve (i.e., if
balance <= reserve then set amountFrom to "0"), compute fractionNumerator with
BigInt(Math.floor(fraction * 1_000_000)) and fractionAmount = (maxAmount *
fractionNumerator) / 1_000_000n, then
setAmountFrom(formatWeiToDecimalString(fractionAmount)); ensure no Number()
conversions are used.

Comment on lines +286 to +294
<TorusToUSD
torusAmount={parseFloat(
fromChain.balance
.replace(" TORUS", "")
.replace(",", ""),
)}
usdPrice={usdPrice}
decimals={2}
/>
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

Don’t parse formatted balance strings for USD; pass raw BigInt.

Parsing UI strings is brittle (only removes first comma). Use the actual balances.

Apply:

-                          <TorusToUSD
-                            torusAmount={parseFloat(
-                              fromChain.balance
-                                .replace(" TORUS", "")
-                                .replace(",", ""),
-                            )}
-                            usdPrice={usdPrice}
-                            decimals={2}
-                          />
+                          <TorusToUSD
+                            torusAmount={
+                              fromChain.name === "Base"
+                                ? (baseBalance ?? 0n)
+                                : (nativeBalance.data ?? 0n)
+                            }
+                            usdPrice={usdPrice}
+                            decimals={2}
+                          />
📝 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
<TorusToUSD
torusAmount={parseFloat(
fromChain.balance
.replace(" TORUS", "")
.replace(",", ""),
)}
usdPrice={usdPrice}
decimals={2}
/>
<TorusToUSD
torusAmount={
fromChain.name === "Base"
? (baseBalance ?? 0n)
: (nativeBalance.data ?? 0n)
}
usdPrice={usdPrice}
decimals={2}
/>
🤖 Prompt for AI Agents
In apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx around
lines 286-294, don’t parse the formatted UI balance string ("... TORUS" with
commas); instead pass the underlying raw balance value (BigInt or integer
string) to TorusToUSD. Replace the parseFloat(fromChain.balance.replace(...))
usage with the raw balance property (e.g. fromChain.rawBalance or
fromChain.balanceBigInt) and convert it to a decimal safely (Number(rawBalance)
/ 10**decimals or use a BigInt-to-decimal helper) or change TorusToUSD to accept
BigInt directly. Remove reliance on string trimming/comma removal so formatting
bugs don’t affect USD calculation and ensure correct decimal scaling is applied.

Comment on lines +334 to +342
<TorusToUSD
torusAmount={parseFloat(
toChain.balance
.replace(" TORUS", "")
.replace(",", ""),
)}
usdPrice={usdPrice}
decimals={2}
/>
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

Same: use raw BigInt for “To” USD instead of parsing strings.

Apply:

-                          <TorusToUSD
-                            torusAmount={parseFloat(
-                              toChain.balance
-                                .replace(" TORUS", "")
-                                .replace(",", ""),
-                            )}
-                            usdPrice={usdPrice}
-                            decimals={2}
-                          />
+                          <TorusToUSD
+                            torusAmount={
+                              toChain.name === "Base"
+                                ? (baseBalance ?? 0n)
+                                : (nativeBalance.data ?? 0n)
+                            }
+                            usdPrice={usdPrice}
+                            decimals={2}
+                          />
📝 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
<TorusToUSD
torusAmount={parseFloat(
toChain.balance
.replace(" TORUS", "")
.replace(",", ""),
)}
usdPrice={usdPrice}
decimals={2}
/>
<TorusToUSD
torusAmount={
toChain.name === "Base"
? (baseBalance ?? 0n)
: (nativeBalance.data ?? 0n)
}
usdPrice={usdPrice}
decimals={2}
/>
🤖 Prompt for AI Agents
apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx around lines
334-342: the current code parses a formatted token string with parseFloat for
the "To" USD component; instead use the raw BigInt value (or construct a BigInt
from the unformatted numeric string) and pass that BigInt into TorusToUSD.
Remove parseFloat and use the unformatted integer representation (strip
suffix/commas if needed) and create a BigInt, and update the TorusToUSD
call/site to accept a BigInt (or convert there) so the USD calculation uses
integer math rather than floating-point parsing.

Comment on lines +356 to +366
<div className="space-y-2">
<Label className="text-sm font-medium">Amount</Label>
<Input
type="number"
value={amountFrom}
onChange={(e) => setAmountFrom(e.target.value)}
placeholder="0.00"
disabled={isTransferInProgress}
className="w-full"
/>
<FractionButtons
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

Allow decimal input without native validation errors.

Add step="any" so browsers accept fractional amounts cleanly.

Apply:

               <Input
                 type="number"
+                step="any"
                 value={amountFrom}
                 onChange={(e) => setAmountFrom(e.target.value)}
                 placeholder="0.00"
                 disabled={isTransferInProgress}
                 className="w-full"
               />
📝 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="space-y-2">
<Label className="text-sm font-medium">Amount</Label>
<Input
type="number"
value={amountFrom}
onChange={(e) => setAmountFrom(e.target.value)}
placeholder="0.00"
disabled={isTransferInProgress}
className="w-full"
/>
<FractionButtons
<div className="space-y-2">
<Label className="text-sm font-medium">Amount</Label>
<Input
type="number"
step="any"
value={amountFrom}
onChange={(e) => setAmountFrom(e.target.value)}
placeholder="0.00"
disabled={isTransferInProgress}
className="w-full"
/>
<FractionButtons
🤖 Prompt for AI Agents
In apps/torus-bridge/src/app/simple/_components/simple-bridge-form.tsx around
lines 356 to 366, the numeric Amount input currently rejects decimals on some
browsers; add the attribute step="any" to the Input component (keeping
type="number") so fractional amounts are accepted without native validation
errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant