Skip to content

feat: use swap options from subgraph#890

Merged
dan13ram merged 5 commits intostagingfrom
feat/swap-options-subgraph
Mar 4, 2026
Merged

feat: use swap options from subgraph#890
dan13ram merged 5 commits intostagingfrom
feat/swap-options-subgraph

Conversation

@dan13ram
Copy link
Collaborator

@dan13ram dan13ram commented Mar 2, 2026

  • Wait for subgraphs to index and test flows
  • Fix like button popup flicker

Summary by CodeRabbit

Release Notes

New Features

  • Token metadata now includes token names and symbols for payment options
  • Improved decimal formatting for token amounts with precision-aware truncation

Style & UX

  • Extended like action feedback duration from 3 to 10 seconds
  • Refined card hover animation with improved positioning
  • Updated transaction link labels for clearer explorer navigation
  • Enhanced token balance visibility in swap interface

Refactor

  • Simplified internal swap data pipeline and sources

@vercel
Copy link

vercel bot commented Mar 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
testnet-nouns-builder Ready Ready Preview Mar 4, 2026 1:31pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
nouns-builder Ignored Ignored Mar 4, 2026 1:31pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

Warning

Rate limit exceeded

@dan13ram has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 30 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a884f91-818b-43be-8a9a-6b9be83365fc

📥 Commits

Reviewing files that changed from the base of the PR and between 2254dc4 and 0f7c994.

📒 Files selected for processing (5)
  • packages/hooks/src/useSwapOptions.ts
  • packages/ui/src/SwapWidget/SwapWidget.css.ts
  • packages/ui/src/SwapWidget/SwapWidget.tsx
  • packages/utils/src/numbers.test.ts
  • packages/utils/src/numbers.ts
📝 Walkthrough

Walkthrough

This PR significantly refactors the swap options infrastructure by shifting logic from client-side utilities to server-side subgraph queries. It removes swap path and options builders, replaces an API endpoint with a subgraph query, and updates the SDK to expose payment option metadata including token names and symbols. UI components are updated to use the new data flow and formatting utilities.

Changes

Cohort / File(s) Summary
Subgraph Schema & Utilities
apps/subgraph/schema.graphql, apps/subgraph/src/utils/coinInfo.ts, apps/subgraph/src/utils/swapPath.ts
Added UNKNOWN coin type variant, extended CoinInfo with name and symbol properties, and populated PaymentOption metadata with token names/symbols from coin data. Subgraph now constructs payment options with complete token metadata.
SDK Subgraph Layer
packages/sdk/src/subgraph/fragments/*, packages/sdk/src/subgraph/queries/swapRoute.graphql, packages/sdk/src/subgraph/requests/swapRouteQuery.ts, packages/sdk/src/subgraph/index.ts, packages/sdk/src/subgraph/sdk.generated.ts, packages/sdk/codegen.yml
Added new GraphQL fragments (PaymentOption, SwapHop, SwapRoute) and query for fetching swap routes. Generated SDK types expose SwapRouteFragment and related types. New swapRouteRequest function validates and fetches swap routes from the subgraph with error handling.
Removed Swap Utilities
packages/swap/src/buildSwapPath.ts, packages/swap/src/buildSwapOptions.ts, packages/swap/src/getCoinInfo.ts, packages/swap/src/index.ts
Deleted client-side swap path builder, options builder, and coin info fetching/caching logic. Removed related exports from the swap package public API.
Refactored Hooks
packages/hooks/src/useSwapOptions.ts, packages/hooks/src/useSwapPath.ts, packages/hooks/src/index.ts
Replaced API-based swap options retrieval with swapRouteRequest from subgraph SDK. Deleted useSwapPath hook. Added convertSwapRouteToOptions function to transform SwapRouteFragment into SwapOption[] with internal helpers for hop normalization and token type conversion.
Type Definitions
packages/swap/src/types.ts
Added TokenInfo type with address, symbol, name, and coin type; added SwapOption interface with token, path, and isDirectSwap flag.
Removed API Endpoint
apps/web/src/pages/api/coins/swap-options.ts
Deleted Next.js API route that previously served serialized swap options. Functionality replaced by subgraph query and SDK integration.
Number Formatting Utilities
packages/utils/src/numbers.ts, packages/utils/src/numbers.test.ts
Added DecimalFormatMode and DecimalFormatOptions types, formatDecimalValue helper for flexible decimal formatting, and formatTokenAmount for consistent token amount display with truncation. Updated formatPrice to delegate to new formatter. Added test coverage for formatTokenAmount.
UI Component Updates
packages/ui/src/SwapWidget/SwapWidget.tsx, packages/ui/src/LikeButton/LikeButton.tsx, packages/dao-ui/src/components/Cards/Cards.css.ts
Updated SwapWidget to use new swapRouteRequest flow and formatTokenAmount for token balance/amount displays. Changed explorer link text to "Explorer". Extended like button timeout from 3s to 10s. Modified card hover animation from transform to top positioning.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Frontend<br/>(useSwapOptions)
    participant SDK as SDK<br/>(swapRouteRequest)
    participant Subgraph as Subgraph API
    participant Convert as convertSwapRoute<br/>ToOptions

    Client->>SDK: swapRouteRequest(coinAddress, chainId)
    SDK->>SDK: Validate address
    SDK->>Subgraph: Query SwapRoute(coinAddress)
    Subgraph-->>SDK: SwapRouteFragment<br/>(paymentOptions, mainPath, coinInfo)
    SDK-->>Client: SwapRouteFragment
    Client->>Convert: convertSwapRouteToOptions<br/>(swapRoute, isBuying, chainId)
    Convert->>Convert: normalizeHopsForTradeDirection<br/>convertCoinType<br/>sanitizeHopRange
    Convert->>Convert: Build TokenInfo from<br/>payment options metadata
    Convert->>Convert: Construct SwapPath<br/>with hops
    Convert->>Convert: Generate ETH/WETH<br/>options
    Convert-->>Client: SwapOption[]<br/>(with tokenName, tokenSymbol)
    Client->>Client: Render swap UI<br/>with formatted amounts
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A hop, skip, and query away,
We've moved the swaps from dapps to array,
The subgraph now builds, the SDK sings,
With token names on metadata wings!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is incomplete and does not follow the provided template. It only contains two unchecked checkboxes without actual description content, motivation context, or proper sections. Complete the PR description by filling in the Description section with details about the changes, Motivation & context explaining why the subgraph migration was needed, and any relevant Code review notes. Ensure all required template sections are addressed.
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: use swap options from subgraph' directly and clearly summarizes the primary architectural change: migrating swap options retrieval from an API-based approach to a subgraph-based approach, which is the main focus of the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/swap-options-subgraph

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.

@dan13ram dan13ram marked this pull request as draft March 2, 2026 13:39
Base automatically changed from fix/zora-coining to staging March 3, 2026 06:27
@dan13ram dan13ram force-pushed the feat/swap-options-subgraph branch from dd29796 to 639d9c2 Compare March 3, 2026 06:28
@dan13ram dan13ram marked this pull request as ready for review March 3, 2026 06:28
@dan13ram dan13ram marked this pull request as draft March 4, 2026 08:30
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

🧹 Nitpick comments (2)
packages/hooks/src/useSwapOptions.ts (1)

4-4: Use import type for type-only imports from @buildeross/swap.

CoinInfo, SwapOption, and SwapPath are used exclusively as type annotations and assertions (lines 9, 12, 81, 118, 129, 141, 146, 183, 189, 194, 210). They are all exported as type-only constructs (export type or export interface). Using import type clarifies intent and ensures better tree-shaking behavior.

🔧 Proposed fix
-import { CoinInfo, SwapOption as SwapOptionType, SwapPath } from '@buildeross/swap'
+import type { CoinInfo, SwapOption as SwapOptionType, SwapPath } from '@buildeross/swap'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/hooks/src/useSwapOptions.ts` at line 4, The import currently brings
in CoinInfo, SwapOption (aliased SwapOptionType), and SwapPath as runtime
imports; change that to a type-only import by using "import type { CoinInfo,
SwapOption as SwapOptionType, SwapPath } from '@buildeross/swap'". This
clarifies they are only used as types (e.g., in uses inside useSwapOptions.ts
such as CoinInfo, SwapOptionType, and SwapPath at lines referenced) and enables
better tree-shaking without altering any runtime behavior.
packages/sdk/src/subgraph/sdk.generated.ts (1)

9464-9469: Add explicit ordering for paymentOptions in SwapRoute fragment

Line 9467 currently fetches paymentOptions without ordering. Downstream code in packages/hooks/src/useSwapOptions.ts iterates through this array to build swap options without defensive sorting, making the order directly impact the UI presentation. Since mainPath is explicitly ordered by hopIndex, adding explicit ordering to paymentOptions (for example by startHopIndex) ensures deterministic ordering across updates.

Suggested source-level change (apply in packages/sdk/src/subgraph/fragments/SwapRoute.graphql, then regenerate)
-  paymentOptions {
+  paymentOptions(orderBy: startHopIndex, orderDirection: asc) {
     ...PaymentOption
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sdk/src/subgraph/sdk.generated.ts` around lines 9464 - 9469, The
SwapRoute fragment fetches paymentOptions without an explicit order which makes
UI ordering non-deterministic; update the SwapRoute GraphQL fragment (the
fragment used to generate packages/sdk/src/subgraph/sdk.generated.ts) to add an
orderBy clause for paymentOptions (for example orderBy: startHopIndex,
orderDirection: asc) so paymentOptions is consistently ordered like mainPath
(orderBy: hopIndex), then regenerate the SDK so the generated file and
downstream useSwapOptions.ts consume the ordered array deterministically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/subgraph/src/utils/swapPath.ts`:
- Around line 244-252: The code currently defaults
tokenType/tokenName/tokenSymbol to CoinType.WETH/'Wrapped Ether'/'WETH' when
loadCoinInfo(tokenBytes) returns null, which can corrupt PaymentOption metadata;
instead, stop using WETH as a silent fallback: call loadCoinInfo(tokenBytes) and
if it returns null, set tokenType/tokenName/tokenSymbol to null/undefined (or a
clear sentinel like 'UNKNOWN') and add a guard so you do not persist a
PaymentOption with incorrect WETH metadata (or skip creating the PaymentOption
entirely); update the same logic used around the other occurrence (the similar
block at lines ~260-261) to follow the same non-WETH-fallback behavior and
ensure downstream code that writes PaymentOption handles null/sentinel values
safely.

In `@packages/hooks/src/useSwapOptions.ts`:
- Around line 121-125: The code uses subgraph-provided indices
(paymentOption.startHopIndex, paymentOption.endHopIndex) to slice
swapRoute.mainPath without checks; validate and sanitize these indices before
slicing in useSwapOptions.ts (the loop building relevantHops and the similar
logic at lines ~162-165). Ensure startHopIndex and endHopIndex are integers,
clamp them to the range [0, swapRoute.mainPath.length - 1], and enforce start <=
end (skip or mark the payment option invalid if not). Add a small guard around
the slice (or skip the paymentOption) and optionally log/warn when indices were
out-of-bounds to make issues detectable.
- Around line 57-69: The convertCoinType function currently maps unknown
coinType values to 'weth', which silently mislabels unsupported tokens; change
convertCoinType to either return a narrower union plus undefined (e.g.,
'eth'|'weth'|'zora-coin'|'clanker-token'|undefined) or throw a descriptive Error
on unknown inputs, update its signature accordingly, and adjust all call sites
in this module (the other token-mapping occurrence in the same file) to
explicitly handle undefined/throw (skip the option or surface the error) instead
of assuming 'weth'; ensure the function name convertCoinType and every place
that consumes its result performs explicit validation before building swap
options.

In `@packages/swap/src/types.ts`:
- Around line 256-259: SwapOption.token is declared as CoinInfo but the mapper
in useSwapOptions.ts only supplies address/name/symbol/type and currently casts,
making the type contract unsound; define a narrower type (e.g., TokenInfo or
MinimalCoinInfo) containing only the fields actually provided (address, name,
symbol, type), change SwapOption.token to that new type, and update the mapper
in useSwapOptions.ts to construct and return that TokenInfo without casting to
CoinInfo so the public type matches actual runtime shape (leave CoinInfo for
places that truly need full pool metadata).

---

Nitpick comments:
In `@packages/hooks/src/useSwapOptions.ts`:
- Line 4: The import currently brings in CoinInfo, SwapOption (aliased
SwapOptionType), and SwapPath as runtime imports; change that to a type-only
import by using "import type { CoinInfo, SwapOption as SwapOptionType, SwapPath
} from '@buildeross/swap'". This clarifies they are only used as types (e.g., in
uses inside useSwapOptions.ts such as CoinInfo, SwapOptionType, and SwapPath at
lines referenced) and enables better tree-shaking without altering any runtime
behavior.

In `@packages/sdk/src/subgraph/sdk.generated.ts`:
- Around line 9464-9469: The SwapRoute fragment fetches paymentOptions without
an explicit order which makes UI ordering non-deterministic; update the
SwapRoute GraphQL fragment (the fragment used to generate
packages/sdk/src/subgraph/sdk.generated.ts) to add an orderBy clause for
paymentOptions (for example orderBy: startHopIndex, orderDirection: asc) so
paymentOptions is consistently ordered like mainPath (orderBy: hopIndex), then
regenerate the SDK so the generated file and downstream useSwapOptions.ts
consume the ordered array deterministically.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 884575c6-ca53-41dc-9a49-fd25e8d25605

📥 Commits

Reviewing files that changed from the base of the PR and between 00a6eec and 7170be8.

📒 Files selected for processing (23)
  • apps/subgraph/schema.graphql
  • apps/subgraph/src/utils/coinInfo.ts
  • apps/subgraph/src/utils/swapPath.ts
  • apps/web/src/pages/api/coins/swap-options.ts
  • packages/dao-ui/src/components/Cards/Cards.css.ts
  • packages/hooks/src/index.ts
  • packages/hooks/src/useSwapOptions.ts
  • packages/hooks/src/useSwapPath.ts
  • packages/sdk/codegen.yml
  • packages/sdk/src/subgraph/fragments/PaymentOption.graphql
  • packages/sdk/src/subgraph/fragments/SwapHop.graphql
  • packages/sdk/src/subgraph/fragments/SwapRoute.graphql
  • packages/sdk/src/subgraph/index.ts
  • packages/sdk/src/subgraph/queries/swapRoute.graphql
  • packages/sdk/src/subgraph/requests/swapRouteQuery.ts
  • packages/sdk/src/subgraph/sdk.generated.ts
  • packages/swap/src/buildSwapOptions.ts
  • packages/swap/src/buildSwapPath.ts
  • packages/swap/src/getCoinInfo.ts
  • packages/swap/src/index.ts
  • packages/swap/src/types.ts
  • packages/ui/src/LikeButton/LikeButton.tsx
  • packages/ui/src/SwapWidget/SwapWidget.tsx
💤 Files with no reviewable changes (7)
  • packages/hooks/src/index.ts
  • packages/hooks/src/useSwapPath.ts
  • packages/swap/src/getCoinInfo.ts
  • packages/swap/src/index.ts
  • apps/web/src/pages/api/coins/swap-options.ts
  • packages/swap/src/buildSwapPath.ts
  • packages/swap/src/buildSwapOptions.ts

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/hooks/src/useSwapOptions.ts`:
- Around line 36-43: The mapped hop construction in useSwapOptions.ts returns
hop.hooks possibly as null which violates the SwapPathHop contract expecting an
optional Address (undefined, not null); update the mapping in the
relevantHops.map (and the similar block at lines ~47-54) to normalize nullable
hooks to undefined (e.g., replace the direct cast hop.hooks as AddressType |
undefined with an explicit conversion that yields undefined when hop.hooks is
null) so downstream consumers receive undefined instead of null while keeping
other fields as-is.

In `@packages/utils/src/numbers.ts`:
- Around line 109-116: formatTokenAmount currently truncates tiny non-zero
values to "0.00"; update formatTokenAmount to detect when the numeric input is
non-zero but the formatted string equals the minimum-zero representation (from
formatDecimalValue with minDecimals:2 and mode:'truncate') and instead return a
small-nonzero indicator such as "<0.01"; implement this by converting the input
(BigNumber/BigNumberish/string) to a high-precision numeric check (e.g., compare
>0 and < 10^-minDecimals) before/after calling formatDecimalValue and return
"<0.01" (or the computed threshold string) when that condition is met. Ensure
changes are made inside formatTokenAmount and reference formatDecimalValue
usage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 69c67d72-db97-4405-8699-6140ac3b2e2b

📥 Commits

Reviewing files that changed from the base of the PR and between 7170be8 and 6fed9be.

📒 Files selected for processing (8)
  • apps/subgraph/schema.graphql
  • apps/subgraph/src/utils/coinInfo.ts
  • apps/subgraph/src/utils/swapPath.ts
  • packages/hooks/src/useSwapOptions.ts
  • packages/swap/src/types.ts
  • packages/ui/src/SwapWidget/SwapWidget.css.ts
  • packages/ui/src/SwapWidget/SwapWidget.tsx
  • packages/utils/src/numbers.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/subgraph/schema.graphql

Comment on lines +109 to +116
export function formatTokenAmount(value: BigNumber | BigNumberish | string): string {
return formatDecimalValue(value, {
minDecimals: 2,
maxDecimals: 10,
mode: 'truncate',
useGrouping: false,
})
}
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

Very small non-zero token amounts can be displayed as 0.00.

formatTokenAmount currently truncates to 10 decimals with a 2-decimal floor, so dust balances (e.g. 1 wei) render as zero. That can mislead users in balance displays.

💡 Proposed fix
 export function formatTokenAmount(value: BigNumber | BigNumberish | string): string {
-  return formatDecimalValue(value, {
+  const parsed = new BigNumber(typeof value === 'string' ? value : value?.toString())
+  if (!parsed.isFinite()) return '0'
+
+  const minVisible = new BigNumber(1).shiftedBy(-10) // 0.0000000001
+  if (!parsed.isZero() && parsed.abs().isLessThan(minVisible)) {
+    return `<${minVisible.toFixed(10)}`
+  }
+
+  return formatDecimalValue(parsed, {
     minDecimals: 2,
     maxDecimals: 10,
     mode: 'truncate',
     useGrouping: false,
   })
 }
📝 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 formatTokenAmount(value: BigNumber | BigNumberish | string): string {
return formatDecimalValue(value, {
minDecimals: 2,
maxDecimals: 10,
mode: 'truncate',
useGrouping: false,
})
}
export function formatTokenAmount(value: BigNumber | BigNumberish | string): string {
const parsed = new BigNumber(typeof value === 'string' ? value : value?.toString())
if (!parsed.isFinite()) return '0'
const minVisible = new BigNumber(1).shiftedBy(-10) // 0.0000000001
if (!parsed.isZero() && parsed.abs().isLessThan(minVisible)) {
return `<${minVisible.toFixed(10)}`
}
return formatDecimalValue(parsed, {
minDecimals: 2,
maxDecimals: 10,
mode: 'truncate',
useGrouping: false,
})
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/utils/src/numbers.ts` around lines 109 - 116, formatTokenAmount
currently truncates tiny non-zero values to "0.00"; update formatTokenAmount to
detect when the numeric input is non-zero but the formatted string equals the
minimum-zero representation (from formatDecimalValue with minDecimals:2 and
mode:'truncate') and instead return a small-nonzero indicator such as "<0.01";
implement this by converting the input (BigNumber/BigNumberish/string) to a
high-precision numeric check (e.g., compare >0 and < 10^-minDecimals)
before/after calling formatDecimalValue and return "<0.01" (or the computed
threshold string) when that condition is met. Ensure changes are made inside
formatTokenAmount and reference formatDecimalValue usage.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/utils/src/numbers.ts (1)

126-128: Keep zeroFormatted tied to the configured decimals.

At Line 127, minDecimals is hardcoded to 2 even though Line 110 already defines minDecimals. Reusing the variable avoids silent drift later.

Refactor patch
   const zeroFormatted = formatDecimalValue(0, {
-    minDecimals: 2,
+    minDecimals,
     maxDecimals,
     mode,
     useGrouping,
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/utils/src/numbers.ts` around lines 126 - 128, zeroFormatted is being
created with a hardcoded minDecimals of 2 even though a minDecimals variable is
defined earlier; update the call to formatDecimalValue (the zeroFormatted
initialization) to use the existing minDecimals variable instead of the literal
2 so zeroFormatted respects the configured minDecimals and stays in sync with
maxDecimals.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/hooks/src/useSwapOptions.ts`:
- Around line 40-41: The current truthy check `hop.fee ? BigInt(hop.fee) :
undefined` drops legitimate zero fees; change it to a nullish check so zero is
preserved by using `hop.fee != null ? BigInt(hop.fee) : undefined` in the
mapping where `hop` is handled (replace both occurrences around the fee
assignment at the two hop mappings in useSwapOptions.ts); leave the `hooks` cast
as-is.

In `@packages/ui/src/SwapWidget/SwapWidget.tsx`:
- Line 341: Add a decimals: number field to the TokenInfo type and ensure it is
populated from the token metadata at initialization (e.g., when loading token
info for swaps), then replace all uses of formatEther(...) with formatUnits(...,
token.decimals) so amounts render per-token decimals; specifically update the
TokenInfo type (TokenInfo) to include decimals, ensure consumers that build
token objects supply decimals, and change formatting calls in SwapWidget (the
return message and the other flagged amount displays that currently call
formatEther) to use formatUnits(amount, token.decimals).

---

Nitpick comments:
In `@packages/utils/src/numbers.ts`:
- Around line 126-128: zeroFormatted is being created with a hardcoded
minDecimals of 2 even though a minDecimals variable is defined earlier; update
the call to formatDecimalValue (the zeroFormatted initialization) to use the
existing minDecimals variable instead of the literal 2 so zeroFormatted respects
the configured minDecimals and stays in sync with maxDecimals.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 15f5baf7-35bf-47e6-9993-430ae9b60b5d

📥 Commits

Reviewing files that changed from the base of the PR and between 6fed9be and 2254dc4.

📒 Files selected for processing (4)
  • packages/hooks/src/useSwapOptions.ts
  • packages/ui/src/SwapWidget/SwapWidget.tsx
  • packages/utils/src/numbers.test.ts
  • packages/utils/src/numbers.ts

Comment on lines +40 to +41
fee: hop.fee ? BigInt(hop.fee) : undefined,
hooks: hop.hooks ? (hop.hooks as AddressType) : 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

🧩 Analysis chain

🏁 Script executed:

cat -n packages/hooks/src/useSwapOptions.ts | head -80

Repository: BuilderOSS/nouns-builder

Length of output: 2950


🏁 Script executed:

# Check the exact lines mentioned
sed -n '35,55p' packages/hooks/src/useSwapOptions.ts

Repository: BuilderOSS/nouns-builder

Length of output: 837


🏁 Script executed:

# Find type definitions for Hop and hop.fee
rg "type Hop|interface Hop" packages/hooks/src -A 10

Repository: BuilderOSS/nouns-builder

Length of output: 697


🏁 Script executed:

# Check SwapPathHop in the types
rg "SwapPathHop|fee\?:" packages/swap/src/types.ts -A 5 -B 2

Repository: BuilderOSS/nouns-builder

Length of output: 667


🏁 Script executed:

# Check subgraph type definitions for fee
rg "fee.*:" packages/sdk/src/subgraph -A 2 -B 2 -t ts | head -100

Repository: BuilderOSS/nouns-builder

Length of output: 5158


Fix fee coercion to preserve zero-fee hops at lines 40–41 and 51–52.

The truthy check hop.fee ? BigInt(hop.fee) : undefined converts fee: 0 to undefined, losing zero-fee pool information. Use nullish coalescing instead:

Proposed fix
-      fee: hop.fee ? BigInt(hop.fee) : undefined,
+      fee: hop.fee != null ? BigInt(hop.fee) : undefined,

(Apply to both lines 40 and 51)

📝 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
fee: hop.fee ? BigInt(hop.fee) : undefined,
hooks: hop.hooks ? (hop.hooks as AddressType) : undefined,
fee: hop.fee != null ? BigInt(hop.fee) : undefined,
hooks: hop.hooks ? (hop.hooks as AddressType) : undefined,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/hooks/src/useSwapOptions.ts` around lines 40 - 41, The current
truthy check `hop.fee ? BigInt(hop.fee) : undefined` drops legitimate zero fees;
change it to a nullish check so zero is preserved by using `hop.fee != null ?
BigInt(hop.fee) : undefined` in the mapping where `hop` is handled (replace both
occurrences around the fee assignment at the two hop mappings in
useSwapOptions.ts); leave the `hooks` cast as-is.

)?.token.symbol || 'ETH'
: symbol
return `Amount exceeds pool limit (max: ${parseFloat(formatEther(poolMaxAmount!)).toFixed(4)} ${tokenSymbol})`
return `Amount exceeds pool limit (max: ${formatTokenAmount(formatEther(poolMaxAmount!))} ${tokenSymbol})`
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== SwapWidget.tsx file length and formatEther usage ==="
wc -l packages/ui/src/SwapWidget/SwapWidget.tsx
echo ""
echo "=== formatEther calls in SwapWidget.tsx ==="
rg -n 'formatEther' packages/ui/src/SwapWidget/SwapWidget.tsx | head -20

Repository: BuilderOSS/nouns-builder

Length of output: 1036


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== SwapOption/Token interface definitions ==="
rg -n -B2 -A10 'interface.*SwapOption|type.*SwapOption|interface.*Token' packages --type=ts --type=tsx | head -100

echo "=== Token decimals field in SwapOption or related types ==="
rg -n 'decimals' packages --type=ts --type=tsx | grep -i 'swap\|token' | head -30

Repository: BuilderOSS/nouns-builder

Length of output: 159


🏁 Script executed:

#!/bin/bash
set -euoelpipefail

echo "=== Examine specific lines around formatEther calls in SwapWidget.tsx ==="
sed -n '335,350p' packages/ui/src/SwapWidget/SwapWidget.tsx
echo "---"
sed -n '490,500p' packages/ui/src/SwapWidget/SwapWidget.tsx
echo "---"
sed -n '575,590p' packages/ui/src/SwapWidget/SwapWidget.tsx

Repository: BuilderOSS/nouns-builder

Length of output: 742


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== SwapOption/Token interface definitions ==="
rg -n 'interface.*SwapOption|type.*SwapOption' packages --type ts | head -20

echo ""
echo "=== Check for decimals in token-related types ==="
rg -n 'decimals' packages --type ts | head -30

Repository: BuilderOSS/nouns-builder

Length of output: 3102


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Context around line 341 formatEther usage ==="
sed -n '335,350p' packages/ui/src/SwapWidget/SwapWidget.tsx

echo ""
echo "=== Context around line 494 formatEther usage ==="
sed -n '488,502p' packages/ui/src/SwapWidget/SwapWidget.tsx

echo ""
echo "=== Context around line 581 formatEther usage ==="
sed -n '575,590p' packages/ui/src/SwapWidget/SwapWidget.tsx

Repository: BuilderOSS/nouns-builder

Length of output: 1887


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== SwapWidget.tsx imports section ==="
head -40 packages/ui/src/SwapWidget/SwapWidget.tsx

echo ""
echo "=== Check if formatUnits is used elsewhere in the file ==="
rg -n 'formatUnits' packages/ui/src/SwapWidget/SwapWidget.tsx

Repository: BuilderOSS/nouns-builder

Length of output: 1411


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== SwapOption interface definition ==="
sed -n '266,290p' packages/swap/src/types.ts

echo ""
echo "=== TokenInfo interface definition (referenced in useSwapOptions.ts) ==="
rg -n -A15 'export interface TokenInfo|export type TokenInfo' packages/swap/src/types.ts

Repository: BuilderOSS/nouns-builder

Length of output: 749


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Token field types in SwapOption ==="
rg -n -B5 -A20 'export interface SwapOption' packages/swap/src/types.ts

echo ""
echo "=== Check SwapPath and related types ==="
rg -n -A10 'export interface SwapPath|export type.*Token' packages/swap/src/types.ts | head -80

Repository: BuilderOSS/nouns-builder

Length of output: 858


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== How token is used in SwapWidget.tsx context ==="
sed -n '480,510p' packages/ui/src/SwapWidget/SwapWidget.tsx

echo ""
echo "=== Token access patterns ==="
rg -n 'token\.' packages/ui/src/SwapWidget/SwapWidget.tsx | head -20

Repository: BuilderOSS/nouns-builder

Length of output: 2546


Add token decimals to TokenInfo and use token-specific formatting.

Token balances at lines 341, 494, 581, 624, 646, and 651 are formatted with formatEther, which assumes 18 decimals. The TokenInfo type includes address, symbol, name, and type—but no decimals field. This means non-18-decimal tokens (USDC, USDT, etc.) will display incorrect amounts by orders of magnitude (e.g., 1 USDC with 6 decimals would render as 0.000000000001).

Required changes:

  1. Add decimals: number field to the TokenInfo type in packages/swap/src/types.ts
  2. Populate decimals from on-chain ERC20 metadata or swap initialization
  3. Replace formatEther(amount) with formatUnits(amount, decimals) at all flagged lines

Also applies to: 494–495, 581–582, 624–625, 646–652

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/SwapWidget/SwapWidget.tsx` at line 341, Add a decimals:
number field to the TokenInfo type and ensure it is populated from the token
metadata at initialization (e.g., when loading token info for swaps), then
replace all uses of formatEther(...) with formatUnits(..., token.decimals) so
amounts render per-token decimals; specifically update the TokenInfo type
(TokenInfo) to include decimals, ensure consumers that build token objects
supply decimals, and change formatting calls in SwapWidget (the return message
and the other flagged amount displays that currently call formatEther) to use
formatUnits(amount, token.decimals).

@dan13ram dan13ram force-pushed the feat/swap-options-subgraph branch from 2254dc4 to 0f7c994 Compare March 4, 2026 13:24
@dan13ram dan13ram merged commit d319924 into staging Mar 4, 2026
2 of 3 checks passed
@dan13ram dan13ram deleted the feat/swap-options-subgraph branch March 4, 2026 13:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant