Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/limit-order-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lifi/widget': minor
---

Add limit order mode with a limit price card, expiry selector, partial-fill toggle, and linked send/receive amounts.
1 change: 1 addition & 0 deletions packages/widget-light/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export type {
WidgetLightEvents,
WidgetLightFormFieldChanged,
WidgetLightLowAddressActivityConfirmed,
WidgetLightNavigationTabChanged,
WidgetLightRouteExecutionUpdate,
WidgetLightRouteHighValueLoss,
WidgetLightRouteSelected,
Expand Down
7 changes: 7 additions & 0 deletions packages/widget-light/src/shared/widgetLightEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export enum WidgetLightEvent {
DestinationChainTokenSelected = 'destinationChainTokenSelected',
FormFieldChanged = 'formFieldChanged',
LowAddressActivityConfirmed = 'lowAddressActivityConfirmed',
NavigationTabChanged = 'navigationTabChanged',
PageEntered = 'pageEntered',
RouteExecutionCompleted = 'routeExecutionCompleted',
RouteExecutionFailed = 'routeExecutionFailed',
Expand Down Expand Up @@ -61,6 +62,11 @@ export interface WidgetLightLowAddressActivityConfirmed {
chainId: number
}

export interface WidgetLightNavigationTabChanged {
tab: string
previousTab?: string
}

export interface WidgetLightRouteHighValueLoss {
fromAmountUSD: number
toAmountUSD: number
Expand Down Expand Up @@ -126,6 +132,7 @@ export interface WidgetLightEvents {
destinationChainTokenSelected: WidgetLightChainTokenSelected
formFieldChanged: WidgetLightFormFieldChanged
lowAddressActivityConfirmed: WidgetLightLowAddressActivityConfirmed
navigationTabChanged: WidgetLightNavigationTabChanged
pageEntered: string
routeExecutionCompleted: unknown
routeExecutionFailed: WidgetLightRouteExecutionUpdate
Expand Down
2 changes: 1 addition & 1 deletion packages/widget-playground/src/defaultWidgetConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const widgetBaseConfig: WidgetConfig = {
],
variant: 'wide',
// mode: 'split',
// _navigationTabs: ['default', 'private', 'refuel'], ['swap-advanced', 'bridge-advanced', 'limit'],
_navigationTabs: ['limit'],
// hiddenUI: {
// chainSidebar: true,
// },
Expand Down
3 changes: 3 additions & 0 deletions packages/widget-playground/src/utils/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export const widgetEventDescriptions: Record<WidgetEventName, string> = {
'The event fires whenever a form value is changed in the widget.',
[WidgetEvent.LowAddressActivityConfirmed]:
'The event fires when the user confirms proceeding despite a low address activity warning for the specified address and chain.',
[WidgetEvent.NavigationTabChanged]:
'The event fires when the user switches between navigation tabs in the widget header.',
[WidgetEvent.PageEntered]:
'The event fires when the user navigates to a page in the widget.',
[WidgetEvent.RouteExecutionCompleted]:
Expand Down Expand Up @@ -60,6 +62,7 @@ export const widgetEventDisplayOrder: WidgetEventName[] = [
WidgetEvent.DestinationChainTokenSelected,
WidgetEvent.SendToWalletToggled,
WidgetEvent.WidgetExpanded,
WidgetEvent.NavigationTabChanged,
WidgetEvent.PageEntered,
WidgetEvent.FormFieldChanged,
WidgetEvent.SettingUpdated,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { BoxProps } from '@mui/material'
import type React from 'react'
import type { JSX } from 'react'
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
import { LimitPriceCard } from '../LimitPriceCard/LimitPriceCard.js'
import { SwapButton } from '../SwapButton/SwapButton.js'
import { CardContainer } from './AmountInputCard.style.js'
import { ReceiveAmountCard } from './ReceiveAmountCard.js'
Expand All @@ -15,6 +16,7 @@ export const AmountInputCardPair: React.FC<BoxProps> = (props): JSX.Element => {

return (
<CardContainer {...props}>
{mode === 'limit' ? <LimitPriceCard sx={{ marginBottom: 1 }} /> : null}
<SendAmountCard mask={showSwapButton} />
<SwapButton sx={{ visibility: showSwapButton ? 'visible' : 'hidden' }} />
<ReceiveAmountCard mask={showSwapButton} />
Expand Down
27 changes: 16 additions & 11 deletions packages/widget/src/components/AmountInputCard/PercentageChips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { type JSX, memo } from 'react'
import { useTranslation } from 'react-i18next'
import { useAvailableChains } from '../../hooks/useAvailableChains.js'
import { useGasRecommendation } from '../../hooks/useGasRecommendation.js'
import { useLinkedLimitFields } from '../../hooks/useLinkedLimitFields.js'
import { useTokenAddressBalance } from '../../hooks/useTokenAddressBalance.js'
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
import type { FormTypeProps } from '../../stores/form/types.js'
Expand All @@ -18,7 +19,8 @@ export const PercentageChips: React.NamedExoticComponent<FormTypeProps> = memo(
const { t } = useTranslation()
const { getChainById } = useAvailableChains()
const { setFieldValue } = useFieldActions()
const { disabledUI } = useWidgetConfig()
const { disabledUI, mode } = useWidgetConfig()
const { setSendAmount } = useLinkedLimitFields()

const amountKey = FormKeyHelper.getAmountKey(formType)
const isDisabled = !!disabledUI?.[amountKey as keyof DisabledUIConfig]
Expand Down Expand Up @@ -48,26 +50,29 @@ export const PercentageChips: React.NamedExoticComponent<FormTypeProps> = memo(
return maxAmount
}

// In limit mode the send amount must flow through the linked-field
// derivation so the receive amount recomputes; otherwise it is a plain
// form-field write.
const applyAmount = (value: string): void => {
if (mode === 'limit') {
setSendAmount(value)
} else {
setFieldValue(amountKey, value, { isTouched: true })
}
}

const handlePercentage = (percentage: number): void => {
const maxAmount = getMaxAmount()
if (maxAmount && token?.decimals) {
const percentageAmount = (maxAmount * BigInt(percentage)) / 100n
setFieldValue(
FormKeyHelper.getAmountKey(formType),
formatUnits(percentageAmount, token.decimals),
{ isTouched: true }
)
applyAmount(formatUnits(percentageAmount, token.decimals))
}
}

const handleMax = (): void => {
const maxAmount = getMaxAmount()
if (maxAmount && token?.decimals) {
setFieldValue(
FormKeyHelper.getAmountKey(formType),
formatUnits(maxAmount, token.decimals),
{ isTouched: true }
)
applyAmount(formatUnits(maxAmount, token.decimals))
}
}

Expand Down
Loading
Loading