Skip to content
Merged
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/components/Claim/Link/Initial.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ export const InitialClaimLinkView = ({
{/* Manual Input Section - Always visible in non-peanut-only mode */}
{!isPeanutWallet && !!claimToExternalWallet && (
<GeneralRecipientInput
placeholder="Enter an address or ENS"
placeholder="Enter a username, an address or ENS"
recipient={recipient}
onUpdate={(update: GeneralRecipientUpdate) => {
setRecipient(update.recipient)
Expand Down
43 changes: 0 additions & 43 deletions src/components/Global/AddressInput/index.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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',
Expand Down
32 changes: 14 additions & 18 deletions src/components/Global/GeneralRecipientInput/index.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)) {
Expand All @@ -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
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -32,8 +31,6 @@ const DirectRequestInitialView = ({ username }: DirectRequestInitialViewProps) =
fileUrl: undefined,
rawFile: undefined,
})
const [isValidRecipient, setIsValidRecipient] = useState(false)
const [inputChanging, setInputChanging] = useState<boolean>(false)
const [currentInputValue, setCurrentInputValue] = useState<string>('')
const [view, setView] = useState<'initial' | 'confirm' | 'success'>('initial')
const { setLoadingState, loadingState, isLoading } = useContext(loadingStateContext)
Expand All @@ -45,7 +42,6 @@ const DirectRequestInitialView = ({ username }: DirectRequestInitialViewProps) =
showError: boolean
errorMessage: string
}>({ showError: false, errorMessage: '' })
const [recipientType, setRecipientType] = useState<RecipientType>('address')
const resetRequestState = () => {
setView('initial')
setCurrentInputValue('')
Expand Down Expand Up @@ -152,25 +148,20 @@ const DirectRequestInitialView = ({ username }: DirectRequestInitialViewProps) =
/>
{!authUser?.user.userId && (
<GeneralRecipientInput
placeholder="Enter an address or ENS"
placeholder="Enter a username, an address or ENS"
recipient={recipient}
onUpdate={(update: GeneralRecipientUpdate) => {
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}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/components/TransactionDetails/TransactionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ const TransactionCard: React.FC<TransactionCardProps> = ({
<div className="flex flex-col">
{/* display formatted name (address or username) */}
<div className="flex flex-row items-center gap-2">
{isPending && <div className="h-2 w-2 animate-pulsate rounded-full bg-pink-1" />}
{isPending && <div className="bg-pink-1 h-2 w-2 animate-pulsate rounded-full" />}
<div className="max-w-40 truncate font-roboto text-[16px] font-medium">
<AddressLink address={name} isLink={false} />
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Withdraw/views/Initial.withdraw.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export default function InitialWithdrawView({ amount, onReview, onBack, isProces
{/* <TokenSelector viewType="withdraw" /> */}

<GeneralRecipientInput
placeholder="Enter an address or ENS"
placeholder="Enter a username, an address or ENS"
recipient={recipient}
onUpdate={(update: GeneralRecipientUpdate) => {
setRecipient(update.recipient)
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useRecentRecipients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/interfaces.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/lib/validation/recipient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/validation/recipient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down