Skip to content

Commit

Permalink
Allow erc20 token address in full or lower letter case (#278)
Browse files Browse the repository at this point in the history
* allow erc20 token address in full or lower letter case

* skip address checksum warning if address is all upper or all lower case

* parse address input

* check invalid checksum addresses
  • Loading branch information
jubalm authored Sep 23, 2024
1 parent 12ddd7f commit 2572963
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 37 deletions.
28 changes: 10 additions & 18 deletions app/ts/components/TokenAdd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ const AddTokenDialog = ({ children }: { children: ComponentChildren }) => {
useSignalEffect(setClickListenerForDialog)

return (
<dialog ref={ ref } class='w-full text-white backdrop:bg-black/80 backdrop:backdrop-blur-[2px] max-w-full max-h-full md:max-w-fit md:max-h-[calc(100vh-3rem)] md:max-w-fit bg-transparent' onSubmit={ handleDialogSubmit }>
<dialog ref={ ref } class='w-full text-white backdrop:bg-black/80 backdrop:backdrop-blur-[2px] max-w-full max-h-full md:max-h-[calc(100vh-3rem)] md:max-w-fit bg-transparent' onSubmit={ handleDialogSubmit }>
{ children }
</dialog>
)
Expand Down Expand Up @@ -162,7 +162,9 @@ const QueryAddressField = () => {

const parsedAddress = EthereumAddress.safeParse(inputField.value)
if (!parsedAddress.success) {
event.target.setCustomValidity('Requires a valid ERC20 contract address')
let errorMessage = 'Requires a valid ERC20 contract address'
if (parsedAddress.message === 'Invalid address checksum.') { errorMessage = parsedAddress.message }
event.target.setCustomValidity(errorMessage)
event.target.reportValidity()
return
}
Expand All @@ -180,7 +182,7 @@ const QueryAddressField = () => {
}

const QueryStatus = () => {
const { address, tokenQuery } = useTokenQuery()
const { tokenQuery } = useTokenQuery()

switch (tokenQuery.value.state) {
case 'inactive':
Expand All @@ -205,31 +207,21 @@ const QueryStatus = () => {
</div>
)
case 'resolved':
const token = serialize(ERC20Token, tokenQuery.value.value)
// queried token will have included a checksum address
const fetchedToken = tokenQuery.value.value

return (
<div class='px-4 py-3 border border-dashed border-white/30 grid grid-cols-1 gap-y-2'>
<div class='text-white/50 text-sm'>Found a matching address</div>
<div class='grid grid-cols-1 gap-y-1'>
<div><span class='font-bold'>{ token.name }</span> <span class='text-white/50'>({ token.symbol })</span></div>
<pre class='px-3 py-2 border border-white/10 font-mono bg-white/10 text-white/80 text-sm'>{ token.address }</pre>
<div><span class='font-bold'>{ fetchedToken.name }</span> <span class='text-white/50'>({ fetchedToken.symbol })</span></div>
<pre class='px-3 py-2 border border-white/10 font-mono bg-white/10 text-white/80 text-sm'>{ fetchedToken.address }</pre>
</div>
<AddressChecksumWarning show={ token.address !== address.value } />
</div>
)
}
}

const AddressChecksumWarning = ({ show }: { show?: boolean }) => {
if (!show) return <></>
return (
<>
<p class='text-amber-500 text-sm'>You entered an address that does not have the correct checksum and Lunaria tried to fetch the address without it.</p>
<label class='flex items-center gap-x-2 text-sm'><input type='checkbox' required /> I've verified the result is indeed the token contract I want to add.</label>
</>
)
}

const TokenDataToFields = () => {
const { cache } = useTokenManager()
const { tokenQuery } = useTokenQuery()
Expand All @@ -241,7 +233,7 @@ const TokenDataToFields = () => {

return (
<>
{ Object.keys(token).map(key => <input type='hidden' name={ key } value={ token[key as keyof typeof token] } />) }
{ Object.entries(token).map(([key, value]) => <input type='hidden' name={ key } value={ value } />)}
<button type='submit' class='px-4 py-3 border border-white/50 hover:bg-white/10 hover:border-white text-center outline-none flex gap-x-1 items-center justify-center'><PlusIcon />{ tokenExistsInCache.value ? '' : 'Save and ' }Use</button>
</>
)
Expand Down
14 changes: 0 additions & 14 deletions app/ts/components/TransferResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { useTransfer } from '../context/Transfer.js'
import { humanReadableEthersError, isEthersError } from '../library/errors.js'
import { areEqualStrings } from '../library/utilities.js'
import { CopyButton } from './CopyButton.js'
import { getAddress } from 'ethers'

export const TransferResult = () => {
const { account } = useWallet()
Expand All @@ -19,16 +18,6 @@ export const TransferResult = () => {
return account.value.value === input.value.to
})

const recipientChecksumValid = useComputed(() => {
const inputAddress = input.value.to

if (inputAddress.match(/^(0x)?[0-9a-fA-F]{40}$/)) {
if (getAddress(inputAddress.toLowerCase()) !== inputAddress && inputAddress.toLowerCase() !== inputAddress) return false
}

return true
})

const recipientIsAKnownToken = useComputed(() => tokensCache.value.data.some(token => areEqualStrings(token.address, input.value.to)))

switch (transaction.value.state) {
Expand All @@ -39,9 +28,6 @@ export const TransferResult = () => {
if (recipientIsAKnownToken.value) {
return <ConfirmField title='Warning: Recipient is a token address' description='The recipient address provided is a token contract address and will probably result in a loss of funds.' label='I understand that this will probably result in a loss of funds.' />
}
if (!recipientChecksumValid.value) {
return <ConfirmField title='Warning: Invalid Address Checksum' description='The recipient address does not pass the checksum validation. Please check the address entered has the correct letter-casing.' label='I understand the risk and want to continue anyway.' />
}
return <></>
case 'rejected':
const txError = transaction.value.error
Expand Down
15 changes: 10 additions & 5 deletions app/ts/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getAddress, isHexString, parseUnits } from 'ethers'
import { getAddress, isError, isHexString, parseUnits } from 'ethers'
import * as funtypes from 'funtypes'

export function createCacheParser<T>(funType: funtypes.Codec<T>) {
Expand Down Expand Up @@ -50,9 +50,14 @@ export function createUnitParser(decimals?: bigint): funtypes.ParsedValue<funtyp

export const AddressParser: funtypes.ParsedValue<funtypes.String, string>['config'] = {
parse: value => {
if (!/^(0x)?[0-9a-fA-F]{40}$/.test(value)) return { success: false, message: `${value} is not a valid address.` }
const checksummedAddress = getAddress(value.toLowerCase())
return { success: true, value: checksummedAddress }
try {
const checksummedAddress = getAddress(value)
return { success: true, value: checksummedAddress }
} catch(error) {
let errorMessage =`${value} is not a valid address.`
if (isError(error, 'INVALID_ARGUMENT') && error.message.includes('bad address checksum')) { errorMessage = 'Invalid address checksum.' }
return { success: false, message: errorMessage }
}
},
serialize: funtypes.String.safeParse,
}
Expand Down Expand Up @@ -82,7 +87,7 @@ export const TransferSchema = funtypes.Object({
export type Transfer = funtypes.Static<typeof TransferSchema>

export const TransferRequestInput = funtypes.Object({
to: funtypes.String,
to: EthereumAddress,
amount: BigIntHex,
token: ERC20Token.Or(funtypes.Undefined),
})
Expand Down

0 comments on commit 2572963

Please sign in to comment.