-
Notifications
You must be signed in to change notification settings - Fork 116
Open
Description
## 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) > 0This indicates regular route calculation supports:
- ✅
fromAmountmode (user inputs source amount) - ✅
toAmountmode (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
- User Input Pattern: Users naturally input
fromAmount("How much do I want to pay/deposit?") - Widget Requirement: ContractCall functionality mandates
toAmount("How much should arrive?") - Calculation Dilemma: Developers cannot accurately calculate
toAmountwithout 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
| ContractCallsQuoteRequestToAmountThe 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:
- How to correctly calculate
toAmountin ContractCall scenarios - Provide example code for two-phase calculation
- Explain why the design enforces
toAmountmode
Current Workaround
We currently use a two-phase calculation:
- Phase 1: Let Widget calculate routes in regular mode (without
contractCalls) - Monitor route results, extract
routes[0].toAmount - Phase 2: Use calculated
toAmountto setcontractCalls - 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.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels