Skip to content

Commit

Permalink
Merge pull request #1799 from sushi-labs/chore/lifi-qa
Browse files Browse the repository at this point in the history
chore: xswap x lifi QA
  • Loading branch information
matthewlilley authored Jan 23, 2025
2 parents ac648e2 + 9c68e86 commit d09f6e2
Show file tree
Hide file tree
Showing 16 changed files with 304 additions and 160 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export async function GET(request: NextRequest) {
exchanges: { allow: ['sushiswap'] },
allowSwitchChain: false,
allowDestinationCall: true,
// fee: // TODO: must set up feeReceiver w/ lifi
fee: 0.0025, // 0.25%
},
}),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ export const SimpleSwapTradeButton = () => {
const [checked, setChecked] = useState<boolean>(false)
const { data: routes } = useSwap()

const showPriceImpactWarning = useMemo(() => {
const priceImpactSeverity = warningSeverity(routes?.priceImpact)
return priceImpactSeverity > 3
}, [routes?.priceImpact])

// Reset
useEffect(() => {
if (warningSeverity(routes?.priceImpact) <= 3) {
if (checked && !showPriceImpactWarning) {
setChecked(false)
}
}, [routes])
}, [showPriceImpactWarning, checked])

const checkerAmount = useMemo(() => {
if (!token0) return []
Expand Down Expand Up @@ -48,9 +54,7 @@ export const SimpleSwapTradeButton = () => {
<Checker.Connect fullWidth size="xl">
<Checker.Amounts amounts={checkerAmount} fullWidth size="xl">
<Checker.Guard
guardWhen={
!checked && warningSeverity(routes?.priceImpact) > 3
}
guardWhen={!checked && showPriceImpactWarning}
guardText="Price impact too high"
variant="destructive"
size="xl"
Expand All @@ -74,7 +78,7 @@ export const SimpleSwapTradeButton = () => {
</Checker.Guard>
</Checker.Guard>
</div>
{warningSeverity(routes?.priceImpact) > 3 && (
{showPriceImpactWarning && (
<div className="flex items-start px-4 py-3 mt-4 rounded-xl bg-red/20">
<input
id="expert-checkbox"
Expand Down
66 changes: 43 additions & 23 deletions apps/web/src/lib/swap/cross-chain/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export const crossChainTransactionRequestSchema = z.object({

const _crossChainStepSchema = z.object({
id: z.string(),
type: z.enum(['swap', 'cross', 'lifi']),
type: z.enum(['swap', 'cross', 'lifi', 'protocol']),
tool: z.string(),
toolDetails: crossChainToolDetailsSchema,
action: crossChainActionSchema,
Expand All @@ -130,25 +130,45 @@ export const crossChainStepSchema = _crossChainStepSchema.extend({
includedSteps: z.array(_crossChainStepSchema),
})

export const crossChainRouteSchema = z.object({
id: z.string(),
fromChainId: z.coerce
.number()
.refine((chainId) => isXSwapSupportedChainId(chainId), {
message: `fromChainId must exist in XSwapChainId`,
}),
fromAmount: z.string().transform((amount) => BigInt(amount)),
fromToken: crossChainTokenSchema,
toChainId: z.coerce
.number()
.refine((chainId) => isXSwapSupportedChainId(chainId), {
message: `toChainId must exist in XSwapChainId`,
}),
toAmount: z.string().transform((amount) => BigInt(amount)),
toAmountMin: z.string().transform((amount) => BigInt(amount)),
toToken: crossChainTokenSchema,
gasCostUSD: z.string(),
steps: z.array(crossChainStepSchema),
tags: z.array(z.string()).optional(),
transactionRequest: crossChainTransactionRequestSchema.optional(),
})
export const crossChainRouteSchema = z
.object({
id: z.string(),
fromChainId: z.coerce
.number()
.refine((chainId) => isXSwapSupportedChainId(chainId), {
message: `fromChainId must exist in XSwapChainId`,
}),
fromAmount: z.string().transform((amount) => BigInt(amount)),
fromToken: crossChainTokenSchema,
toChainId: z.coerce
.number()
.refine((chainId) => isXSwapSupportedChainId(chainId), {
message: `toChainId must exist in XSwapChainId`,
}),
toAmount: z.string().transform((amount) => BigInt(amount)),
toAmountMin: z.string().transform((amount) => BigInt(amount)),
toToken: crossChainTokenSchema,
gasCostUSD: z.string(),
steps: z.array(
crossChainStepSchema.transform((data) => {
return {
...data,
includedStepsWithoutFees: data.includedSteps.filter(
(step) => step.tool !== 'feeCollection',
),
}
}),
),
tags: z.array(z.string()).optional(),
transactionRequest: crossChainTransactionRequestSchema.optional(),
})
.refine((data) => data.steps.length === 1, {
message: 'multi-step routes are not supported',
})
.transform((data) => {
const { steps, ...rest } = data
return {
...rest,
step: steps[0],
}
})
59 changes: 52 additions & 7 deletions apps/web/src/lib/swap/cross-chain/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,34 @@ import { Amount, Native, Token, Type } from 'sushi/currency'
import { zeroAddress } from 'viem'
import { CrossChainStep } from './types'

export const getCrossChainStepBreakdown = (step?: CrossChainStep) => {
if (!step)
return {
srcStep: undefined,
bridgeStep: undefined,
dstStep: undefined,
}

const feeIndex = step.includedSteps.findIndex(
(_step) => _step.type === 'protocol',
)

const steps =
feeIndex === -1
? step.includedSteps
: [
...step.includedSteps.slice(0, feeIndex),
...step.includedSteps.slice(feeIndex + 1),
]

const bridgeIndex = steps.findIndex((_step) => _step.type === 'cross')
return {
srcStep: steps[bridgeIndex - 1],
bridgeStep: steps[bridgeIndex],
dstStep: steps[bridgeIndex + 1],
}
}

interface FeeBreakdown {
amount: Amount<Type>
amountUSD: number
Expand All @@ -11,16 +39,23 @@ interface FeeBreakdown {
export interface FeesBreakdown {
gas: Map<ChainId, FeeBreakdown>
protocol: Map<ChainId, FeeBreakdown>
ui: Map<ChainId, FeeBreakdown>
}

enum FeeType {
GAS = 'GAS',
PROTOCOL = 'PROTOCOL',
UI = 'UI',
}

export const getCrossChainFeesBreakdown = (route: CrossChainStep[]) => {
const gasFeesBreakdown = getFeesBreakdown(route, FeeType.GAS)
const protocolFeesBreakdown = getFeesBreakdown(route, FeeType.PROTOCOL)
const UI_FEE_NAME = 'LIFI Shared Fee'

export const getCrossChainFeesBreakdown = (
_steps: CrossChainStep[] | CrossChainStep,
) => {
const steps = Array.isArray(_steps) ? _steps : [_steps]
const gasFeesBreakdown = getFeesBreakdown(steps, FeeType.GAS)
const protocolFeesBreakdown = getFeesBreakdown(steps, FeeType.PROTOCOL)
const gasFeesUSD = Array.from(gasFeesBreakdown.values()).reduce(
(sum, gasCost) => sum + gasCost.amountUSD,
0,
Expand All @@ -29,25 +64,35 @@ export const getCrossChainFeesBreakdown = (route: CrossChainStep[]) => {
(sum, feeCost) => sum + feeCost.amountUSD,
0,
)
const totalFeesUSD = gasFeesUSD + protocolFeesUSD
const totalFeesUSD = gasFeesUSD + protocolFeesUSD // does not include UI fees

const uiFeesBreakdown = getFeesBreakdown(steps, FeeType.UI)
const uiFeesUSD = Array.from(uiFeesBreakdown.values()).reduce(
(sum, feeCost) => sum + feeCost.amountUSD,
0,
)

return {
feesBreakdown: {
gas: gasFeesBreakdown,
protocol: protocolFeesBreakdown,
ui: uiFeesBreakdown,
},
totalFeesUSD,
gasFeesUSD,
protocolFeesUSD,
uiFeesUSD,
}
}

const getFeesBreakdown = (route: CrossChainStep[], feeType: FeeType) => {
return route.reduce((feesByChainId, step) => {
const getFeesBreakdown = (steps: CrossChainStep[], feeType: FeeType) => {
return steps.reduce((feesByChainId, step) => {
const fees =
feeType === FeeType.PROTOCOL
? step.estimate.feeCosts.filter((fee) => fee.included === false)
: step.estimate.gasCosts
: feeType === FeeType.UI
? step.estimate.feeCosts.filter((fee) => fee.name === UI_FEE_NAME)
: step.estimate.gasCosts

if (fees.length === 0) return feesByChainId

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ export const ConfirmationDialogContent: FC<ConfirmationDialogContent> = ({
const { data: trade } = useSelectedCrossChainTradeRoute()

const swapOnDest =
trade?.steps[0] &&
trade?.step &&
[
trade.steps[0].includedSteps[1]?.type,
trade.steps[0].includedSteps[2]?.type,
trade.step.includedStepsWithoutFees[1]?.type,
trade.step.includedStepsWithoutFees[2]?.type,
].includes('swap')
? true
: false
Expand Down Expand Up @@ -98,12 +98,13 @@ export const ConfirmationDialogContent: FC<ConfirmationDialogContent> = ({

if (dialogState.dest === StepState.PartialSuccess) {
const fromTokenSymbol =
routeRef?.current?.steps?.[0]?.includedSteps?.[1]?.type === 'swap'
? routeRef?.current?.steps?.[0]?.includedSteps?.[1]?.action?.fromToken
?.symbol
: routeRef?.current?.steps?.[0]?.includedSteps?.[2]?.type === 'swap'
? routeRef?.current?.steps?.[0]?.includedSteps?.[2]?.action?.fromToken
?.symbol
routeRef?.current?.step?.includedStepsWithoutFees?.[1]?.type === 'swap'
? routeRef?.current?.step?.includedStepsWithoutFees?.[1]?.action
?.fromToken?.symbol
: routeRef?.current?.step?.includedStepsWithoutFees?.[2]?.type ===
'swap'
? routeRef?.current?.step?.includedStepsWithoutFees?.[2]?.action
?.fromToken?.symbol
: undefined

return (
Expand Down
19 changes: 12 additions & 7 deletions apps/web/src/ui/swap/cross-chain/cross-chain-swap-route-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const CrossChainSwapRouteCard: FC<CrossChainSwapRouteCardProps> = ({
state: { token1, chainId0, chainId1 },
} = useDerivedStateCrossChainSwap()

const { data: price } = usePrice({
const { data: price, isLoading: isPriceLoading } = usePrice({
chainId: token1?.chainId,
address: token1?.wrapped.address,
})
Expand Down Expand Up @@ -73,7 +73,7 @@ export const CrossChainSwapRouteCard: FC<CrossChainSwapRouteCardProps> = ({
protocolFeesUSD,
totalFeesUSD,
} = useMemo(() => {
const step = route.steps[0]
const step = route.step
const executionDurationSeconds = step.estimate.executionDuration
const executionDurationMinutes = Math.floor(executionDurationSeconds / 60)

Expand All @@ -83,7 +83,7 @@ export const CrossChainSwapRouteCard: FC<CrossChainSwapRouteCardProps> = ({
: `${executionDurationMinutes} minutes`

const { feesBreakdown, totalFeesUSD, gasFeesUSD, protocolFeesUSD } =
getCrossChainFeesBreakdown(route.steps)
getCrossChainFeesBreakdown(step)

return {
step,
Expand All @@ -93,7 +93,7 @@ export const CrossChainSwapRouteCard: FC<CrossChainSwapRouteCardProps> = ({
gasFeesUSD,
protocolFeesUSD,
}
}, [route?.steps])
}, [route.step])

return (
<Card
Expand All @@ -118,9 +118,14 @@ export const CrossChainSwapRouteCard: FC<CrossChainSwapRouteCardProps> = ({
</CardTitle>
<CardDescription>
{amountOutUSD ? (
<span>{`${amountOutUSD} after fees`}</span>
<span>≈ ${amountOutUSD} after fees</span>
) : (
<span className="w-36">
<span
className={classNames(
'w-36',
!isPriceLoading ? 'invisible' : '',
)}
>
<SkeletonText fontSize="sm" />
</span>
)}
Expand All @@ -145,7 +150,7 @@ export const CrossChainSwapRouteCard: FC<CrossChainSwapRouteCardProps> = ({
</span>
</div>
</CardHeader>
{isSelected && step.includedSteps.length > 1 ? (
{isSelected && step.includedStepsWithoutFees.length > 1 ? (
<>
<Separator className="mb-5" />
<CardContent className="!p-5 !pt-0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const CrossChainSwapRouteMobileCard: FC<
state: { token1, chainId0, chainId1 },
} = useDerivedStateCrossChainSwap()

const { data: price } = usePrice({
const { data: price, isLoading: isPriceLoading } = usePrice({
chainId: token1?.chainId,
address: token1?.wrapped.address,
})
Expand Down Expand Up @@ -59,7 +59,7 @@ export const CrossChainSwapRouteMobileCard: FC<
protocolFeesUSD,
totalFeesUSD,
} = useMemo(() => {
const step = route?.steps[0]
const step = route?.step
if (!step)
return {
step,
Expand All @@ -79,7 +79,7 @@ export const CrossChainSwapRouteMobileCard: FC<
: `${executionDurationMinutes} minutes`

const { feesBreakdown, totalFeesUSD, gasFeesUSD, protocolFeesUSD } =
getCrossChainFeesBreakdown(route.steps)
getCrossChainFeesBreakdown(step)

return {
step,
Expand All @@ -89,7 +89,7 @@ export const CrossChainSwapRouteMobileCard: FC<
gasFeesUSD,
protocolFeesUSD,
}
}, [route?.steps])
}, [route?.step])

return (
<Card
Expand Down Expand Up @@ -119,18 +119,23 @@ export const CrossChainSwapRouteMobileCard: FC<
{amountOut?.toSignificant(6)} {token1?.symbol}
</span>
) : (
<div className="w-28">
<span className="w-28">
<SkeletonText fontSize="sm" />
</div>
</span>
)}
{amountOutUSD ? (
<span className="text-sm text-muted-foreground">
≈ ${amountOutUSD} after fees
</span>
) : (
<div className="w-36">
<span
className={classNames(
'w-36',
!isPriceLoading ? 'invisible' : '',
)}
>
<SkeletonText fontSize="sm" />
</div>
</span>
)}
{route?.tags?.includes(order) ? (
<div className="flex justify-center items-center rounded-full px-2 bg-gradient-to-r from-blue/20 to-pink/20">
Expand Down
Loading

0 comments on commit d09f6e2

Please sign in to comment.