diff --git a/src/components/Claim/Link/Initial.view.tsx b/src/components/Claim/Link/Initial.view.tsx index 26d0eb0d6..02d80b0e5 100644 --- a/src/components/Claim/Link/Initial.view.tsx +++ b/src/components/Claim/Link/Initial.view.tsx @@ -553,7 +553,7 @@ export const InitialClaimLinkView = ({ {/* Manual Input Section - Always visible in non-peanut-only mode */} {!isPeanutWallet && !!claimToExternalWallet && ( { setRecipient(update.recipient) diff --git a/src/components/Global/AddressInput/index.tsx b/src/components/Global/AddressInput/index.tsx deleted file mode 100644 index 2521030dd..000000000 --- a/src/components/Global/AddressInput/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -'use client' -import { isAddress } from 'viem' - -import { resolveEns } from '@/app/actions/ens' -import ValidatedInput, { InputUpdate } from '@/components/Global/ValidatedInput' -import { validateEnsName } from '@/utils' -import * as Sentry from '@sentry/nextjs' - -type AddressInputProps = { - placeholder: string - value: string - onUpdate: (update: InputUpdate) => void - className?: string -} - -const AddressInput = ({ placeholder = 'Enter a valid address', value, onUpdate, className }: AddressInputProps) => { - async function checkAddress(recipient: string): Promise { - try { - if (validateEnsName(recipient)) { - const resolvedAddress = await resolveEns(recipient.toLowerCase()) - return !!resolvedAddress - } else { - return isAddress(recipient) - } - } catch (error) { - Sentry.captureException(error) - console.error('Error while validating recipient input field:', error) - return false - } - } - return ( - - ) -} - -export default AddressInput diff --git a/src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx b/src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx index e3e4968d4..5a2fc9a60 100644 --- a/src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx +++ b/src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx @@ -106,7 +106,7 @@ describe('GeneralRecipientInput Type Detection', () => { expectedType: 'address', expectedValid: false, description: 'too long for US account', - expectedError: 'Invalid Ethereum address', + expectedError: 'Invalid Peanut username', }, // IBAN cases @@ -171,7 +171,7 @@ describe('GeneralRecipientInput Type Detection', () => { expectedType: 'address', expectedValid: false, description: 'invalid ETH address (missing 0x prefix)', - expectedError: 'Invalid Ethereum address', + expectedError: 'Invalid Peanut username', }, // ENS cases @@ -185,7 +185,7 @@ describe('GeneralRecipientInput Type Detection', () => { }, { input: 'not-found.eth', - expectedType: 'ens', + expectedType: 'address', expectedValid: false, description: 'unresolvable ENS name', expectedError: 'ENS name not found', diff --git a/src/components/Global/GeneralRecipientInput/index.tsx b/src/components/Global/GeneralRecipientInput/index.tsx index 7ad74c9f0..4da03b3bc 100644 --- a/src/components/Global/GeneralRecipientInput/index.tsx +++ b/src/components/Global/GeneralRecipientInput/index.tsx @@ -1,14 +1,14 @@ 'use client' -import { resolveEns } from '@/app/actions/ens' import ValidatedInput, { InputUpdate } from '@/components/Global/ValidatedInput' import { useRecentRecipients } from '@/hooks/useRecentRecipients' import * as interfaces from '@/interfaces' -import { validateBankAccount, validateEnsName } from '@/utils' +import { validateBankAccount } from '@/utils' import { formatBankAccountDisplay, sanitizeBankAccount } from '@/utils/format.utils' import * as Senty from '@sentry/nextjs' import { useCallback, useRef } from 'react' import { isIBAN } from 'validator' -import { isAddress } from 'viem' +import { validateAndResolveRecipient } from '@/lib/validation/recipient' +import { BASE_URL } from '@/constants' type GeneralRecipientInputProps = { className?: string @@ -45,7 +45,7 @@ const GeneralRecipientInput = ({ let isValid = false let type: interfaces.RecipientType = 'address' - const trimmedInput = recipient.trim() + const trimmedInput = recipient.trim().replace(`${BASE_URL}/`, '') const sanitizedInput = sanitizeBankAccount(trimmedInput) if (isIBAN(sanitizedInput)) { @@ -55,20 +55,16 @@ const GeneralRecipientInput = ({ } else if (/^[0-9]{1,17}$/.test(sanitizedInput)) { type = 'us' isValid = true - } else if (validateEnsName(trimmedInput)) { - type = 'ens' - const address = await resolveEns(trimmedInput.toLowerCase()) - if (address) { - resolvedAddress.current = address + } else { + try { + const validation = await validateAndResolveRecipient(trimmedInput) isValid = true - } else { - errorMessage.current = 'ENS name not found' - isValid = false + resolvedAddress.current = validation.resolvedAddress + type = validation.recipientType.toLowerCase() as interfaces.RecipientType + } catch (error: unknown) { + errorMessage.current = (error as Error).message + return false } - } else { - type = 'address' - isValid = isAddress(trimmedInput) - if (!isValid) errorMessage.current = 'Invalid Ethereum address' } recipientType.current = type return isValid @@ -84,14 +80,14 @@ const GeneralRecipientInput = ({ const sanitizedValue = recipientType.current === 'iban' || recipientType.current === 'us' ? sanitizeBankAccount(update.value) - : update.value.trim() + : update.value.trim().replace(`${BASE_URL}/`, '') let _update: GeneralRecipientUpdate if (update.isValid) { errorMessage.current = '' _update = { recipient: - 'ens' === recipientType.current + 'ens' === recipientType.current || recipientType.current === 'username' ? { address: resolvedAddress.current, name: sanitizedValue } : { address: sanitizedValue, name: undefined }, type: recipientType.current, diff --git a/src/components/Request/direct-request/views/Initial.direct.request.view.tsx b/src/components/Request/direct-request/views/Initial.direct.request.view.tsx index a7ba9bca8..d615bc1ef 100644 --- a/src/components/Request/direct-request/views/Initial.direct.request.view.tsx +++ b/src/components/Request/direct-request/views/Initial.direct.request.view.tsx @@ -9,7 +9,6 @@ import DirectSuccessView from '@/components/Payment/Views/Status.payment.view' import UserCard from '@/components/User/UserCard' import { loadingStateContext } from '@/context' import { useWallet } from '@/hooks/wallet/useWallet' -import { RecipientType } from '@/interfaces' import { useUserStore } from '@/redux/hooks' import { IAttachmentOptions } from '@/redux/types/send-flow.types' import { ApiUser, usersApi } from '@/services/users' @@ -32,8 +31,6 @@ const DirectRequestInitialView = ({ username }: DirectRequestInitialViewProps) = fileUrl: undefined, rawFile: undefined, }) - const [isValidRecipient, setIsValidRecipient] = useState(false) - const [inputChanging, setInputChanging] = useState(false) const [currentInputValue, setCurrentInputValue] = useState('') const [view, setView] = useState<'initial' | 'confirm' | 'success'>('initial') const { setLoadingState, loadingState, isLoading } = useContext(loadingStateContext) @@ -45,7 +42,6 @@ const DirectRequestInitialView = ({ username }: DirectRequestInitialViewProps) = showError: boolean errorMessage: string }>({ showError: false, errorMessage: '' }) - const [recipientType, setRecipientType] = useState('address') const resetRequestState = () => { setView('initial') setCurrentInputValue('') @@ -152,25 +148,20 @@ const DirectRequestInitialView = ({ username }: DirectRequestInitialViewProps) = /> {!authUser?.user.userId && ( { setRecipient(update.recipient) if (!update.recipient.address) { - setRecipientType('address') setErrorState({ showError: false, errorMessage: '', }) - } else { - setRecipientType(update.type) } - setIsValidRecipient(update.isValid) setErrorState({ showError: !update.isValid, errorMessage: update.errorMessage, }) - setInputChanging(update.isChanging) }} showInfoText={false} /> diff --git a/src/components/TransactionDetails/TransactionCard.tsx b/src/components/TransactionDetails/TransactionCard.tsx index 994306eae..38c008f7e 100644 --- a/src/components/TransactionDetails/TransactionCard.tsx +++ b/src/components/TransactionDetails/TransactionCard.tsx @@ -122,7 +122,7 @@ const TransactionCard: React.FC = ({
{/* display formatted name (address or username) */}
- {isPending &&
} + {isPending &&
}
diff --git a/src/components/Withdraw/views/Initial.withdraw.view.tsx b/src/components/Withdraw/views/Initial.withdraw.view.tsx index 0109f9f14..334a720c3 100644 --- a/src/components/Withdraw/views/Initial.withdraw.view.tsx +++ b/src/components/Withdraw/views/Initial.withdraw.view.tsx @@ -92,7 +92,7 @@ export default function InitialWithdrawView({ amount, onReview, onBack, isProces {/* */} { setRecipient(update.recipient) diff --git a/src/hooks/useRecentRecipients.ts b/src/hooks/useRecentRecipients.ts index 355b00e2f..adfc29a90 100644 --- a/src/hooks/useRecentRecipients.ts +++ b/src/hooks/useRecentRecipients.ts @@ -5,7 +5,7 @@ const MAX_RECENT_ITEMS = 5 interface RecentRecipient { value: string - type: 'address' | 'ens' | 'iban' | 'us' + type: 'address' | 'ens' | 'iban' | 'us' | 'username' timestamp: number } diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts index ff2229a13..5bee118ef 100644 --- a/src/interfaces/interfaces.ts +++ b/src/interfaces/interfaces.ts @@ -1,7 +1,7 @@ import { KYCStatus } from '@/utils' import { interfaces as peanutInterfaces } from '@squirrel-labs/peanut-sdk' -export type RecipientType = 'address' | 'ens' | 'iban' | 'us' +export type RecipientType = 'address' | 'ens' | 'iban' | 'us' | 'username' export interface IResponse { success: boolean diff --git a/src/lib/validation/recipient.test.ts b/src/lib/validation/recipient.test.ts index 3dd6e4a37..635d4e353 100644 --- a/src/lib/validation/recipient.test.ts +++ b/src/lib/validation/recipient.test.ts @@ -49,7 +49,7 @@ describe('Recipient Validation', () => { }) it('should throw for unresolvable ENS names', async () => { - await expect(validateAndResolveRecipient('nonexistent.eth')).rejects.toThrow('Error resolving ENS name') + await expect(validateAndResolveRecipient('nonexistent.eth')).rejects.toThrow('ENS name not found') }) it('should validate Ethereum addresses', async () => { diff --git a/src/lib/validation/recipient.ts b/src/lib/validation/recipient.ts index 8efe51e4a..f05293113 100644 --- a/src/lib/validation/recipient.ts +++ b/src/lib/validation/recipient.ts @@ -19,7 +19,7 @@ export async function validateAndResolveRecipient( // resolve the ENS name to address const resolvedAddress = await resolveEns(recipient) if (!resolvedAddress) { - throw new RecipientValidationError('Error resolving ENS name') + throw new RecipientValidationError('ENS name not found') } return { identifier: recipient,