widget icon indicating copy to clipboard operation
widget copied to clipboard

ContractCall in `custom` subvariant requires `toAmount`, causing UX issues and logical conflicts

Open smartchainark opened this issue 3 months ago • 0 comments

## Issue Description

When using `subvariant: 'custom'` with dynamically generated `contractCalls`, the Widget enforces the requirement of a `toAmount` field to trigger the `getContractCallsQuote` API call. This conflicts with the regular route design and creates implementation difficulties for Deposit/Checkout scenarios.

## Steps to Reproduce

1. Configure Widget with `subvariant: 'custom'` and `subvariantOptions: { custom: 'deposit' }`
2. Dynamically generate `contractCalls` in `contractComponent`
3. User inputs `fromAmount` in the form (e.g., 100 USDC)
4. Attempt to set `toAmount = fromAmount` (assuming 1:1 rate)
5. Widget calls `getContractCallsQuote` and either errors or returns incorrect routes

## Code Example

```typescript
// DepositCard.tsx
const contractCalls: ContractCall[] = useMemo(() => {
  if (!fromAmount || !address || !fromToken || !toChain) {
    return []
  }
  
  // Generate contractCall
  const contractCall: ContractCall = {
    fromAmount: parseUnits(fromAmount, token.decimals).toString(),
    fromTokenAddress: fromToken,
    toContractAddress: LOGGER_CONTRACT,
    toContractCallData: callData,
    toContractGasLimit: '300000',
    toTokenAddress: token.address,
  }
  
  return [contractCall]
}, [fromAmount, fromToken, toChain, token, address])

useEffect(() => {
  if (token) {
    setFieldValue('toChain', token.chainId, { isTouched: true })
    setFieldValue('toToken', token.address, { isTouched: true })
    // ⚠️ Issue: Setting toAmount = fromAmount causes API calculation errors
    setFieldValue('toAmount', fromAmount, { isTouched: true })
  }
  
  if (contractCalls?.length > 0) {
    setFieldValue('contractCalls', contractCalls, { isTouched: true })
  }
}, [contractCalls, token, fromAmount])

Source Code Analysis

1. Regular Routes Support Both Modes

File: packages/widget/src/hooks/useRoutes.ts Line 101

const hasAmount = Number(fromTokenAmount) > 0 || Number(toTokenAmount) > 0

This indicates regular route calculation supports:

  • fromAmount mode (user inputs source amount)
  • toAmount mode (user inputs destination amount)

2. ContractCall Routes Require toAmount

File: packages/widget/src/hooks/useRoutes.ts Line 284

if (subvariant === 'custom' && contractCalls && toAmount) {
  const contractCallQuote = await getContractCallsQuote({
    fromAddress: fromAddress as string,
    fromChain: fromChainId,
    fromToken: fromTokenAddress,
    toAmount: toAmount.toString(), // ⚠️ toAmount is mandatory
    toChain: toChainId,
    toToken: toTokenAddress,
    contractCalls,
    // ...
  })
}

Problem Analysis

Core Contradiction

  1. User Input Pattern: Users naturally input fromAmount ("How much do I want to pay/deposit?")
  2. Widget Requirement: ContractCall functionality mandates toAmount ("How much should arrive?")
  3. Calculation Dilemma: Developers cannot accurately calculate toAmount without calling APIs (need to consider bridge fees, slippage, etc.)

The Problem with 1:1 Setting

If we simply set toAmount = fromAmount:

User inputs: 100 USDC (Polygon)
Setting: toAmount = 100 USDC (Optimism)

Widget calls API:
  "To receive 100 USDC, user needs to pay ~102 USDC"
  
Actual situation:
  User only inputted 100 USDC
  
Result: ❌ Insufficient amount or route calculation error

SDK Supports But Widget Doesn't Implement

While @lifi/sdk's ContractCallsQuoteRequest type supports both modes:

// @lifi/types/src/api.ts
export type ContractCallsQuoteRequestFromAmount = {
  fromAmount: string  // ✅ SDK supports
  // ...
}

export type ContractCallsQuoteRequestToAmount = {
  toAmount: string    // ✅ Widget uses this
  // ...
}

export type ContractCallsQuoteRequest =
  | ContractCallsQuoteRequestFromAmount
  | ContractCallsQuoteRequestToAmount

The Widget implementation only uses the toAmount mode.

Expected Behavior

Option 1: Support fromAmount Mode (Recommended)

Modify useRoutes.ts to support ContractCall based on fromAmount:

if (subvariant === 'custom' && contractCalls) {
  // Check which mode to use
  const hasToAmount = toAmount && Number(toTokenAmount) > 0
  const hasFromAmount = fromAmount && Number(fromTokenAmount) > 0
  
  if (hasToAmount) {
    // Existing logic: use toAmount mode
    const contractCallQuote = await getContractCallsQuote({
      toAmount: toAmount.toString(),
      // ...
    })
  } else if (hasFromAmount) {
    // New logic: use fromAmount mode
    const contractCallQuote = await getContractCallsQuote({
      fromAmount: fromAmount.toString(),
      // ...
    })
  }
}

Option 2: Provide Documentation

If fromAmount mode cannot be technically supported, suggest documenting:

  1. How to correctly calculate toAmount in ContractCall scenarios
  2. Provide example code for two-phase calculation
  3. Explain why the design enforces toAmount mode

Current Workaround

We currently use a two-phase calculation:

  1. Phase 1: Let Widget calculate routes in regular mode (without contractCalls)
  2. Monitor route results, extract routes[0].toAmount
  3. Phase 2: Use calculated toAmount to set contractCalls
  4. Widget recalculates (using getContractCallsQuote)

However, this leads to:

  • Two API calls
  • Complex state management
  • Potential UI flickering

Impact Scope

This issue affects all scenarios using the following combination:

  • subvariant: 'custom'
  • subvariantOptions: { custom: 'deposit' } or 'checkout'
  • Dynamically generated contractCalls
  • Users primarily input fromAmount

Typical scenarios include:

  • Protocol Deposits
  • NFT Checkout
  • Token Staking
  • Custom Contract Interactions

Environment Information

  • Widget Version: 3.x
  • SDK Version: @lifi/sdk 3.x
  • Browsers: Chrome/Safari/Firefox (all browsers)

Related Discussions

  • ContractCall official documentation: https://docs.li.fi/
  • Similar issues: (link if any related issues exist)

Thank you for considering this enhancement request! Happy to provide more information or test cases if needed.

smartchainark avatar Oct 03 '25 17:10 smartchainark