feat(swap-widget): toggle sell input between crypto and fiat amount - #12436
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sync price Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also add the new fiat context fields to existing machine test fixtures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…m comments Omit the sell fiat subtext entirely when the asset has no USD price (a $0.00 would mislead), pull the $ prefix snug against the amount, and drop redundant comments in favor of self-documenting code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 4 minutes and 42 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThe PR adds fiat-mode sell input to the swap widget. It introduces ChangesFiat Sell Input Mode
Sequence Diagram(s)sequenceDiagram
participant User
participant InputStep
participant useSwapHandlers
participant SwapMachine
participant useSellFiatSync
User->>InputStep: clicks fiat toggle button
InputStep->>useSwapHandlers: onToggleSellFiat(sellAssetUsdPrice)
useSwapHandlers->>useSwapHandlers: cryptoToFiat(...) when enabling fiat
useSwapHandlers->>SwapMachine: send SET_SELL_FIAT_MODE { isFiat }
SwapMachine->>SwapMachine: assignSellFiatMode updates context
Note over useSellFiatSync: USD price arrives async
useSellFiatSync->>useSellFiatSync: computeSellFiatSyncAction(...)
useSellFiatSync->>SwapMachine: send SET_SELL_AMOUNT if base-unit changed
User->>InputStep: types fiat amount "$50"
InputStep->>useSwapHandlers: onSellAmountChange("50", sellAssetUsdPrice)
useSwapHandlers->>useSwapHandlers: fiatToCrypto("50", price, precision)
useSwapHandlers->>SwapMachine: send SET_SELL_AMOUNT with crypto amounts + fiatValue
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/swap-widget/src/machines/__tests__/types.test.ts (1)
50-110:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winInclude
SET_SELL_FIAT_MODEin the event-union fixture.Line 50 claims full
SwapMachineEventcoverage, but Lines 51-108 omit the newSET_SELL_FIAT_MODEvariant while still assertingtoHaveLength(20). This leaves the contract test incomplete.Proposed fix
const events: SwapMachineEvent[] = [ @@ { type: 'SET_SELL_AMOUNT', amount: '1.0', amountBaseUnit: '1000000000000000000' }, + { + type: 'SET_SELL_FIAT_MODE', + isFiat: true, + fiatValue: '100', + amount: '0.03125', + amountBaseUnit: '31250000000000000', + }, @@ - expect(events).toHaveLength(20) + expect(events).toHaveLength(21)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/swap-widget/src/machines/__tests__/types.test.ts` around lines 50 - 110, The test labeled 'SwapMachineEvent discriminated union covers all event types' in the types.test.ts file is missing the SET_SELL_FIAT_MODE event variant in its events array. Add a SET_SELL_FIAT_MODE event object to the events array (with appropriate properties for this event type) and update the toHaveLength assertion from 20 to 21 to reflect the complete coverage of all SwapMachineEvent variants.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/swap-widget/src/utils/fiatConversion.ts`:
- Around line 30-31: The return statement that calculates fiat amount by
multiplying cryptoAmount by priceBn and calling toFixed(2) is forcing precision
to 2 decimals, which rounds small non-zero values to "0.00" and unintentionally
clears the sellAmountBaseUnit in the sync flow. Instead of always forcing 2
decimal places, preserve the calculated value with appropriate precision that
allows small non-zero amounts to remain visible and maintain the underlying
crypto amount value. Adjust the toFixed call to use a precision level that
balances display requirements without collapsing legitimate non-zero values.
---
Outside diff comments:
In `@packages/swap-widget/src/machines/__tests__/types.test.ts`:
- Around line 50-110: The test labeled 'SwapMachineEvent discriminated union
covers all event types' in the types.test.ts file is missing the
SET_SELL_FIAT_MODE event variant in its events array. Add a SET_SELL_FIAT_MODE
event object to the events array (with appropriate properties for this event
type) and update the toHaveLength assertion from 20 to 21 to reflect the
complete coverage of all SwapMachineEvent variants.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8ecd6d27-5b3d-419f-ac30-97430a634e22
📒 Files selected for processing (12)
packages/swap-widget/src/components/InputStep.tsxpackages/swap-widget/src/components/SwapWidget.csspackages/swap-widget/src/components/SwapWidget.tsxpackages/swap-widget/src/hooks/useSellFiatSync.tspackages/swap-widget/src/hooks/useSwapHandlers.tspackages/swap-widget/src/machines/__tests__/guards.test.tspackages/swap-widget/src/machines/__tests__/swapMachine.test.tspackages/swap-widget/src/machines/__tests__/types.test.tspackages/swap-widget/src/machines/swapMachine.tspackages/swap-widget/src/machines/types.tspackages/swap-widget/src/utils/__tests__/fiatConversion.test.tspackages/swap-widget/src/utils/fiatConversion.ts
…IAT_MODE mode-only SET_SELL_AMOUNT now carries the full sell amount (crypto amount, base unit, fiat string) and SET_SELL_FIAT_MODE only flips the mode and seeds the fiat display. This removes the overloaded event and lets handleSwapTokens clear the amount with a single unconditional dispatch instead of branching on fiat mode. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/swap-widget/src/hooks/useSwapHandlers.ts (1)
16-19: ⚡ Quick winAdd explicit return types on modified handlers and exported hook result.
Lines 45-46 and Line 67-68 introduce/modify handler signatures without explicit return types, and Lines 165-175 return an inferred object shape. Please type these explicitly to match the TS ruleset.
Suggested diff
type UseSwapHandlersParams = { partnerCode?: string allowShapeshiftRedirect: boolean } + +type UseSwapHandlersResult = { + handleSwapTokens: () => void + handleSellAssetSelect: (asset: Asset) => void + handleBuyAssetSelect: (asset: Asset) => void + handleSellAmountChange: (value: string, sellAssetUsdPrice?: string) => void + handleToggleSellFiat: (sellAssetUsdPrice?: string) => void + handleSelectRate: (rate: TradeRate) => void + handleSlippageChange: (value: string) => void + redirectToShapeShift: () => void + handleButtonClick: () => void +} export const useSwapHandlers = ({ partnerCode, allowShapeshiftRedirect, -}: UseSwapHandlersParams) => { +}: UseSwapHandlersParams): UseSwapHandlersResult => { @@ - const handleSellAmountChange = useCallback( - (value: string, sellAssetUsdPrice?: string) => { + const handleSellAmountChange = useCallback( + (value: string, sellAssetUsdPrice?: string): void => { @@ - const handleToggleSellFiat = useCallback( - (sellAssetUsdPrice?: string) => { + const handleToggleSellFiat = useCallback( + (sellAssetUsdPrice?: string): void => {As per coding guidelines, "
**/*.{ts,tsx}: ALWAYS use explicit types for function parameters and return values in TypeScript".Also applies to: 45-46, 67-68, 165-175
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/swap-widget/src/hooks/useSwapHandlers.ts` around lines 16 - 19, Add explicit return type annotations to all function handlers and the exported useSwapHandlers hook to comply with the TypeScript ruleset. Specifically, add return types to the handler functions at lines 45-46 and 67-68, and add a return type annotation to the useSwapHandlers hook itself (lines 165-175) instead of relying on type inference. Ensure each modified handler and the hook's return value have clear, explicit type signatures that match the actual data being returned.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/swap-widget/src/hooks/useSwapHandlers.ts`:
- Around line 16-19: Add explicit return type annotations to all function
handlers and the exported useSwapHandlers hook to comply with the TypeScript
ruleset. Specifically, add return types to the handler functions at lines 45-46
and 67-68, and add a return type annotation to the useSwapHandlers hook itself
(lines 165-175) instead of relying on type inference. Ensure each modified
handler and the hook's return value have clear, explicit type signatures that
match the actual data being returned.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 1ee902b3-c1dd-4928-a6f1-c74c72adcfae
📒 Files selected for processing (8)
packages/swap-widget/src/components/InputStep.tsxpackages/swap-widget/src/hooks/useSwapHandlers.tspackages/swap-widget/src/machines/__tests__/swapMachine.test.tspackages/swap-widget/src/machines/__tests__/types.test.tspackages/swap-widget/src/machines/swapMachine.tspackages/swap-widget/src/machines/types.tspackages/swap-widget/src/utils/__tests__/fiatConversion.test.tspackages/swap-widget/src/utils/fiatConversion.ts
💤 Files with no reviewable changes (1)
- packages/swap-widget/src/components/InputStep.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
- packages/swap-widget/src/machines/tests/types.test.ts
- packages/swap-widget/src/machines/types.ts
- packages/swap-widget/src/machines/swapMachine.ts
- packages/swap-widget/src/utils/fiatConversion.ts
- packages/swap-widget/src/utils/tests/fiatConversion.test.ts
…toToFiat
SET_SELL_FIAT_MODE now carries only { isFiat } and SET_SELL_AMOUNT is the sole
writer of the sell amount triple (amount, base unit, fiat string). Entering fiat
mode seeds the fiat string via SET_SELL_AMOUNT then flips the mode; exiting is a
bare mode flip. Also rename cryptoToFiatInput -> cryptoToFiat.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
♻️ Duplicate comments (1)
packages/swap-widget/src/utils/fiatConversion.ts (1)
20-30:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPreserve tiny non-zero fiat when seeding fiat mode.
Line 30 forces two decimals for an editable value. Tiny non-zero crypto can become
"0.00", and the fiat sync path can then clearsellAmountBaseUnit. Use higher precision (or trim trailing zeros) when generating the fiat input seed.Proposed minimal fix
export const cryptoToFiat = ( amountBaseUnit: string | undefined, price: string, precision: number, ): string => { @@ const cryptoAmount = BigAmount.fromBaseUnit({ value: amountBaseUnit, precision }).toPrecision() - return bn(cryptoAmount).times(priceBn).toFixed(2) + const fiatValue = bn(cryptoAmount).times(priceBn) + return fiatValue.toFixed(Math.max(2, precision)).replace(/\.?0+$/, '') }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/swap-widget/src/utils/fiatConversion.ts` around lines 20 - 30, The cryptoToFiat function's return statement uses .toFixed(2) which forces exactly two decimal places, causing tiny but non-zero fiat amounts to round to "0.00" and break fiat mode seeding. Instead of forcing two decimals, apply higher precision to the return value or trim trailing zeros to preserve the actual calculated amount so that small non-zero values are not lost when used as input seeds in fiat mode.
🧹 Nitpick comments (1)
packages/swap-widget/src/machines/types.ts (1)
38-38: ⚡ Quick winUse a more specific boolean payload name for fiat mode.
On Line 38,
isFiatis valid but ambiguous in this state machine. PreferisSellAmountFiatto match context naming and reduce cross-layer confusion in handlers/tests.Suggested diff
- | { type: 'SET_SELL_FIAT_MODE'; isFiat: boolean } + | { type: 'SET_SELL_FIAT_MODE'; isSellAmountFiat: boolean }As per coding guidelines, “Use descriptive boolean variable names with
is,has,can,shouldprefixes” and “Use camelCase for variables, functions, and methods with descriptive names that explain the purpose.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/swap-widget/src/machines/types.ts` at line 38, The boolean payload name `isFiat` in the SET_SELL_FIAT_MODE action type is ambiguous and does not clearly indicate what is being set to fiat mode. Rename the `isFiat` property to `isSellAmountFiat` throughout the codebase to be more descriptive and match the context naming conventions. This change should be applied to the type definition in the state machine types, as well as all places where this action is dispatched and handled to ensure consistency across handlers and tests.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@packages/swap-widget/src/utils/fiatConversion.ts`:
- Around line 20-30: The cryptoToFiat function's return statement uses
.toFixed(2) which forces exactly two decimal places, causing tiny but non-zero
fiat amounts to round to "0.00" and break fiat mode seeding. Instead of forcing
two decimals, apply higher precision to the return value or trim trailing zeros
to preserve the actual calculated amount so that small non-zero values are not
lost when used as input seeds in fiat mode.
---
Nitpick comments:
In `@packages/swap-widget/src/machines/types.ts`:
- Line 38: The boolean payload name `isFiat` in the SET_SELL_FIAT_MODE action
type is ambiguous and does not clearly indicate what is being set to fiat mode.
Rename the `isFiat` property to `isSellAmountFiat` throughout the codebase to be
more descriptive and match the context naming conventions. This change should be
applied to the type definition in the state machine types, as well as all places
where this action is dispatched and handled to ensure consistency across
handlers and tests.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ac1bc496-3992-4f6e-8d35-6355ea25f825
📒 Files selected for processing (6)
packages/swap-widget/src/hooks/useSwapHandlers.tspackages/swap-widget/src/machines/__tests__/swapMachine.test.tspackages/swap-widget/src/machines/swapMachine.tspackages/swap-widget/src/machines/types.tspackages/swap-widget/src/utils/__tests__/fiatConversion.test.tspackages/swap-widget/src/utils/fiatConversion.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/swap-widget/src/machines/swapMachine.ts
- packages/swap-widget/src/hooks/useSwapHandlers.ts
- packages/swap-widget/src/machines/tests/swapMachine.test.ts
- packages/swap-widget/src/utils/tests/fiatConversion.test.ts
…vert on no price - computeSellFiatSyncAction: revert to crypto entry before the empty-fiat check so a price-less asset in fiat mode never strands the user, and only fill crypto when it's missing instead of reconciling — never overwrite an exact amount (no cent-snapping). - handleToggleSellFiat: keep the exact crypto on entering fiat mode (seed fiat for display only), so toggling crypto->fiat->crypto round-trips the amount unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/swap-widget/src/utils/fiatConversion.ts`:
- Around line 52-60: The validation check on line 52 for sellAssetUsdPrice only
catches falsy values, but invalid prices like '0' or non-finite strings pass
through and cause fiatToCrypto to fail while still emitting SET_SELL_AMOUNT with
empty values. Update the condition on line 52 to validate that sellAssetUsdPrice
is not just truthy but also represents a valid positive number (not zero, NaN,
or non-finite), and return the same fallback response of { type:
'SET_SELL_FIAT_MODE', isFiat: false } for all invalid prices to maintain
consistent behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 52b0fda3-007b-4ede-8d1e-7cafc275da0c
📒 Files selected for processing (6)
packages/swap-widget/src/components/SwapWidget.tsxpackages/swap-widget/src/hooks/useSellFiatSync.tspackages/swap-widget/src/hooks/useSwapHandlers.tspackages/swap-widget/src/machines/swapMachine.tspackages/swap-widget/src/utils/__tests__/fiatConversion.test.tspackages/swap-widget/src/utils/fiatConversion.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- packages/swap-widget/src/hooks/useSellFiatSync.ts
- packages/swap-widget/src/hooks/useSwapHandlers.ts
- packages/swap-widget/src/machines/swapMachine.ts
- packages/swap-widget/src/components/SwapWidget.tsx
- packages/swap-widget/src/utils/tests/fiatConversion.test.ts
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Normalize sellAssetUsdPrice to undefined when the market price is missing, zero, or non-finite, so fiat-mode gating (toggle visibility, handlers, useSellFiatSync revert) handles price-less assets consistently instead of letting '0' slip through as a valid price. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Description
Lets users flip the swap widget's sell input between entering a crypto amount and entering a fiat (USD) amount. Tapping the USD subtext under the sell field toggles the mode: in fiat mode the user types a dollar amount (shown with a
$prefix) and the equivalent crypto is used for quoting/execution, with the crypto equivalent shown as the subtext.Design notes:
sellAmount/sellAmountBaseUnitstay always-crypto — the single source of truth read by quoting, approval, execution, guards and redirect is untouched. Fiat is a thin input/display layer (isSellAmountFiat,sellAmountFiat) on top.SET_SELL_AMOUNTcarries the full sell amount (crypto amount, base unit, and the fiat string), whileSET_SELL_FIAT_MODEonly flips the mode and seeds the fiat display. Mode and amount are orthogonal, so e.g. clearing the amount on swap-direction is a single unconditional dispatch.fiatToCrypto/cryptoToFiatInput.useSellFiatSyncreconciles the crypto amount when the USD price arrives asynchronously or the sell asset changes while in fiat mode (the machine has no access to price).$0.00); switching to a price-less asset while in fiat mode falls back to crypto entry.Issue (if applicable)
closes #12417
closes SS-5696
https://linear.app/shapeshift-dao/issue/SS-5696/unable-to-type-in-a-dollar-amount-to-swap
Risk
Low risk. Changes are isolated to the swap-widget sell input (UI + sell-amount state/handlers). No new or modified on-chain transactions — quoting/approval/execution still consume the same
sellAmountBaseUnitcrypto value as before; fiat is converted to that crypto value before any quote is requested.None directly. Only the sell amount the user enters changes representation; the downstream crypto base-unit value and all swapper/quote/approval paths are unchanged.
Testing
Engineering
Unit tests (
pnpm --filter @shapeshiftoss/swap-widget test) cover the pure converters (fiatToCrypto,cryptoToFiatInput), the sync decision (computeSellFiatSyncAction), and the machine fiat-mode state transitions (SET_SELL_AMOUNTcarrying fiat,SET_SELL_FIAT_MODEflipping mode, crypto cleared on asset change in fiat mode).Manual (demo app under
packages/swap-widget/src/demo):$USD value with a ⇅ icon.$prefix); subtext reads≈ <crypto> <SYMBOL>.≈ <crypto>subtext).$amount persists, crypto recomputes.$0.00.Operations
User-facing: in the swap widget, on the Sell field, tap the USD value line beneath the amount to switch between entering a token amount and a dollar amount. Verify entering a dollar amount produces a correct quote and the displayed token equivalent is sensible.
Screenshots (if applicable)
Summary by CodeRabbit