Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/common/hooks/useAddressInput/useAddressInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ const useAddressInput = ({
])

useEffect(() => {
const trimmedAddress = fieldValue.trim()
const trimmedAddress = (fieldValue || '').trim()
const dotIndexInAddress = trimmedAddress.indexOf('.')
// There is a dot and it is not the first or last character
const canBeDomain =
Expand Down
87 changes: 44 additions & 43 deletions src/web/modules/PPv1/transfer/screens/TransferScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ const TransferScreen = () => {
)

// Privacy Pools state
const controllerAmountFieldValue = amountFieldMode === 'token' ? withdrawalAmount : amountInFiat
const controllerAmountFieldValue = (amountFieldMode === 'token' ? withdrawalAmount : amountInFiat) || ''
const [amountFieldValue, setAmountFieldValue] = useSyncedState<string>({
backgroundState: controllerAmountFieldValue,
updateBackgroundState: (newAmount) => {
Expand All @@ -131,7 +131,7 @@ const TransferScreen = () => {
forceUpdateOnChangeList: [programmaticUpdateCounter, amountFieldMode]
})
const [addressStateFieldValue, setAddressStateFieldValue] = useSyncedState<string>({
backgroundState: addressState.fieldValue,
backgroundState: addressState.fieldValue || '',
updateBackgroundState: (newAddress: string) => {
handleUpdateForm({ addressState: { fieldValue: newAddress } })
},
Expand All @@ -140,7 +140,7 @@ const TransferScreen = () => {

// Railgun state syncing
const railgunControllerAmountFieldValue =
railgunAmountFieldMode === 'token' ? railgunWithdrawalAmount : railgunAmountInFiat
(railgunAmountFieldMode === 'token' ? railgunWithdrawalAmount : railgunAmountInFiat) || ''
const [railgunAmountFieldValue, setRailgunAmountFieldValue] = useSyncedState<string>({
backgroundState: railgunControllerAmountFieldValue,
updateBackgroundState: (newAmount) => {
Expand All @@ -149,7 +149,7 @@ const TransferScreen = () => {
forceUpdateOnChangeList: [railgunProgrammaticUpdateCounter, railgunAmountFieldMode]
})
const [railgunAddressStateFieldValue, setRailgunAddressStateFieldValue] = useSyncedState<string>({
backgroundState: railgunAddressState.fieldValue,
backgroundState: railgunAddressState.fieldValue || '',
updateBackgroundState: (newAddress: string) => {
handleRailgunUpdateForm({ addressState: { fieldValue: newAddress } })
},
Expand Down Expand Up @@ -382,11 +382,11 @@ const TransferScreen = () => {
}

const amountErrorMessage = useMemo(() => {
if (!withdrawalAmount || withdrawalAmount.trim() === '') return ''
if (!amountFieldValue || amountFieldValue.trim() === '') return ''
if (!poolInfo) return ''

try {
const amount = parseUnits(withdrawalAmount, 18)
const amount = parseUnits(amountFieldValue, 18)

if (amount < poolInfo.minWithdrawal) {
return `Minimum transfer amount is ${formatUnits(poolInfo.minWithdrawal, 18)} ETH`
Expand All @@ -409,7 +409,7 @@ const TransferScreen = () => {
} catch (error) {
return 'Invalid amount'
}
}, [withdrawalAmount, totalApprovedBalance.total, poolInfo, relayerQuote])
}, [amountFieldValue, totalApprovedBalance.total, poolInfo, relayerQuote])

// Privacy Pools form validation
const isTransferFormValid = useMemo(() => {
Expand Down Expand Up @@ -437,11 +437,20 @@ const TransferScreen = () => {
const railgunForm = useRailgunForm()
const railgunTotalPrivateBalancesFormatted = railgunForm.totalPrivateBalancesFormatted

// Railgun amount error message
// Railgun amount error message - immediate validation for UX
const railgunAmountErrorMessage = useMemo(() => {
if (!railgunWithdrawalAmount || railgunWithdrawalAmount.trim() === '') return ''
const amountToValidate = railgunAmountFieldMode === 'token'
? railgunAmountFieldValue
: railgunWithdrawalAmount

if (!amountToValidate || amountToValidate.trim() === '') return ''
if (!railgunSelectedToken) return ''

// First check controller's validation (format, non-zero) if available
if (railgunWithdrawalAmount && !railgunValidationFormMsgs.amount.success) {
return railgunValidationFormMsgs.amount.message
}

try {
// Get the balance for the selected token
const tokenAddressLower = railgunSelectedToken.address?.toLowerCase()
Expand All @@ -452,7 +461,7 @@ const TransferScreen = () => {
}

const decimals = balanceInfo.decimals || railgunSelectedToken.decimals || 18
const amount = parseUnits(railgunWithdrawalAmount, decimals)
const amount = parseUnits(amountToValidate, decimals)
const availableBalance = BigInt(balanceInfo.amount)

if (amount > availableBalance) {
Expand All @@ -463,24 +472,24 @@ const TransferScreen = () => {
} catch (error) {
return 'Invalid amount'
}
}, [railgunWithdrawalAmount, railgunSelectedToken, railgunTotalPrivateBalancesFormatted])
}, [
railgunAmountFieldValue,
railgunWithdrawalAmount,
railgunAmountFieldMode,
railgunValidationFormMsgs.amount,
railgunSelectedToken,
railgunTotalPrivateBalancesFormatted
])

// Railgun form validation
const isRailgunTransferFormValid = useMemo(() => {
// Use the synced state value for address check, or fall back to controller state
const addressValue = railgunAddressStateFieldValue || railgunAddressState.fieldValue
const hasAmount =
railgunAmountFieldValue &&
railgunAmountFieldValue !== '0' &&
parseFloat(railgunAmountFieldValue) > 0
// Use controller's validation (with debounce) for proper validation
const hasAmount = !!railgunWithdrawalAmount && railgunWithdrawalAmount !== '0'
const hasToken = !!railgunSelectedToken
const hasAddress = addressValue && addressValue.trim() !== ''

// For address validation, check if the address input state says it's valid
// OR if we have an address value and no explicit error
const addressIsValid =
!railgunAddressInputState.validation.isError ||
(hasAddress && !railgunAddressInputState.validation.message)
const hasAddress = !!railgunAddressState.fieldValue || !!railgunAddressState.ensAddress

// Use controller's validation messages
const addressIsValid = railgunValidationFormMsgs.recipientAddress.success
const noAmountError = !railgunAmountErrorMessage

// Debug logging (can be removed later)
Expand All @@ -491,26 +500,23 @@ const TransferScreen = () => {
hasAddress,
addressIsValid,
noAmountError,
amountFieldValue: railgunAmountFieldValue,
addressFieldValue: addressValue,
syncedAddressFieldValue: railgunAddressStateFieldValue,
controllerAddressFieldValue: railgunAddressState.fieldValue,
withdrawalAmount: railgunWithdrawalAmount,
addressFieldValue: railgunAddressState.fieldValue,
ensAddress: railgunAddressState.ensAddress,
selectedToken: railgunSelectedToken?.symbol,
addressError: railgunAddressInputState.validation.isError,
addressValidationMessage: railgunAddressInputState.validation.message,
validationMsgs: railgunValidationFormMsgs,
amountError: railgunAmountErrorMessage
})
}

return !!(hasAmount && hasToken && hasAddress && addressIsValid && noAmountError)
}, [
railgunAmountFieldValue,
railgunWithdrawalAmount,
railgunAmountErrorMessage,
railgunSelectedToken,
railgunAddressInputState.validation.isError,
railgunAddressInputState.validation.message,
railgunAddressStateFieldValue,
railgunAddressState.fieldValue
railgunValidationFormMsgs,
railgunAddressState.fieldValue,
railgunAddressState.ensAddress
])

const onBack = useCallback(() => {
Expand Down Expand Up @@ -603,20 +609,17 @@ const TransferScreen = () => {
setIsSubmitting(true)
let isInternalTransfer = false
try {
// Use synced state values (current input values) instead of controller state
// Controller state may be debounced and not updated yet
const amount = railgunAmountFieldValue || railgunWithdrawalAmount
// Prioritize synced state field value, then ENS resolved address, then addressInputState.address
const amount = railgunWithdrawalAmount
// Prioritize ENS resolved address, then synced state field value, then addressInputState.address
const address =
railgunAddressStateFieldValue ||
railgunAddressState.ensAddress ||
railgunAddressStateFieldValue ||
railgunAddressInputState.address

// Debug logging
console.log('handleRailgunWithdrawal - Input values:', {
amount,
address,
amountFieldValue: railgunAmountFieldValue,
withdrawalAmount: railgunWithdrawalAmount,
addressStateFieldValue: railgunAddressStateFieldValue,
addressInputStateAddress: railgunAddressInputState.address,
Expand All @@ -630,7 +633,6 @@ const TransferScreen = () => {
token: railgunSelectedToken,
amount,
address,
amountFieldValue: railgunAmountFieldValue,
withdrawalAmount: railgunWithdrawalAmount,
addressInputState: railgunAddressInputState.address,
addressStateFieldValue: railgunAddressStateFieldValue,
Expand Down Expand Up @@ -777,7 +779,6 @@ const TransferScreen = () => {
}, [
railgunForm,
railgunSelectedToken,
railgunAmountFieldValue,
railgunWithdrawalAmount,
railgunAddressInputState.address,
railgunAddressStateFieldValue,
Expand Down