UNPKG

agsola-react-ens-address

Version:
369 lines (329 loc) 11.9 kB
import { normalize } from 'viem/ens' import React, { useState, useEffect, useRef, isValidElementType, useCallback, } from 'react' import PropTypes from 'prop-types' import _ from 'lodash' import { getEthAddressType, isAddress, ETH_ADDRESS_TYPE, } from './utils/address.js' import Loader from './Loader.js' import { SingleNameBlockies } from './Blockies.js' import { PublicClient } from 'viem' //import './style.css' Import in your own website const ENS_NOT_FOUND = 'ENS name not found' function Address(props) { const [resolvedAddress, setResolvedAddress] = useState(null) const inputValue = props.inputValue; const setInputValue = props.setterFunction; //const [inputValue, setInputValue] = useState('') const [isResolvingInProgressValue, setIsResolvingInProgress] = useState(0); const isResolvingInProgress = isResolvingInProgressValue > 0; const [error, setError] = useState(null) const [avatarURL, setAvatarURL] = useState(null) //const [ENS, setENS] = useState(null) const [provider, setProvider] = useState<PublicClient>(null); const currentInput = useRef() const inputDebouncerHandler = async (input) => { try { // Regular expression to check if input is a domain const domainRegex = new RegExp('^[^.]{1,63}(\\.[^.]{2,3})+$'); const starProtocolDomainRegex = new RegExp('^[^.]{1,63}\\.(zk|linea|line|base|bas|ba|lin|li)$'); const spaceIdDomainRegex = new RegExp('^[^.]{1,63}\\.(bnb|arb|bn|ar)$'); if (starProtocolDomainRegex.test(input)) { if (input.endsWith('.zk')) { const urlToFetch = `https://omniapi.zkns.app/domain-resolver/getRecord/${input}`; // console.log("1urlToFetch", urlToFetch); const fetchData = await fetch(urlToFetch); const data = await fetchData.text(); // console.log("starProtocolDomain data: ", data); if (isAddress(data)) { setError(null); setResolvedAddress(data); props.onResolve({ address: data, name: input, type: ETH_ADDRESS_TYPE.address, }); props.onError(null); } else { console.error("starProtocolDomain: Invalid domain"); } } else if (input.endsWith('.linea') | input.endsWith('.base')) { const urlToFetch = `https://mainnet-api.sns.so/domain/getRecord/${input}`; // console.log("2urlToFetch", urlToFetch); const result = await fetch(urlToFetch); // console.log("starProtocolDomain result: ", result); const json = await result.json(); // console.log("starProtocolDomain json: ", json); const { data, code, message } = json; // console.log("starProtocolDomain", data, code, message); if (code === 200 && isAddress(data)) { setError(null); setResolvedAddress(data); props.onResolve({ address: data, name: input, type: ETH_ADDRESS_TYPE.address, }); props.onError(null); } else { console.error("starProtocolDomain: Invalid domain"); } } else { // Todavía no ha llegado aquí. setError(null); } } else if (spaceIdDomainRegex.test(input)) { if (input.endsWith('.arb') || input.endsWith('.bnb')) { let tld; if (input.endsWith('.arb')) { tld = "arb1"; } else { tld = "bnb"; } const urlToFetch = `https://api.prd.space.id/v1/getAddress?tld=${tld}&domain=${input}`; // console.log("spaceId urlToFetch:", urlToFetch); const result = await fetch(urlToFetch); // console.log("spaceId result: ", result); const json = await result.json(); // console.log("spaceId json: ", json); const { code, address } = json; // console.log("spaceId code, address: ", code, address); if (code === 0 && isAddress(address) && address !== "0x0000000000000000000000000000000000000000") { setError(null); setResolvedAddress(address); props.onResolve({ address: address, name: input, type: ETH_ADDRESS_TYPE.address, }); props.onError(null); } else { console.error("starProtocolDomain: Invalid domain"); } } } else if (domainRegex.test(input)) { const result = await resolveName(input) if (input === currentInput.current) { if (error !== null) setError(null); const { address, type, name, avatar } = result; if (type === ETH_ADDRESS_TYPE.name) { setResolvedAddress(address) } else if (type === ETH_ADDRESS_TYPE.address) { setResolvedAddress(name) } setAvatarURL(avatar) props.onResolve(result) props.onError(null) } } else { if (error) setError(null); } //if newest continue, otherwise ignore } catch (error) { setError(error.toString()) setResolvedAddress(null) setAvatarURL(null) props.onResolve({ address: input, name: null, type: null, }) props.onError(error) } } const inputDebouncer = _.debounce(inputDebouncerHandler, 500) useEffect(() => { setProvider(props.provider); /* async function setup() { const options = {} if(props.ensAddress){ options.ensAddress = props.ensAddress } if (props.provider) { options.customProvider = props.provider } const { ens } = await setupENS(options) setENS(ens) } setup() */ }, [props.provider]) const handleInput = useCallback( async (address) => { if (!address || address.length === 0) { setInputValue('') setError(null) setResolvedAddress(null) if (inputDebouncer) { inputDebouncer.cancel() } } setInputValue(address) if (inputDebouncer) { inputDebouncer(address) } }, [inputDebouncer] ) useEffect(() => { if (props.presetValue.length !== 0) { handleInput(props.presetValue) } }, [props.presetValue]) useEffect(() => { if (props.inputValue.length === 0) { handleInput(props.inputValue) } }, [props.inputValue]) if (!provider) { return <Loader className="loader" /> } const handleResolver = async (fn) => { try { // console.log("ags BEFORE RESOLVER"); setIsResolvingInProgress((current) => current+1); setResolvedAddress(null) return await fn() } catch (error) { if (error && error.message && error.message === ENS_NOT_FOUND) return throw error } finally { setIsResolvingInProgress((current) => current-1); // console.log("ags AFTER RESOLVER"); } } const resolveName = async (inputValue) => { // update latest input resolving currentInput.current = inputValue const addressType = getEthAddressType(inputValue) const publicClient: PublicClient = provider; if (addressType === ETH_ADDRESS_TYPE.name) { return await handleResolver(async () => { const resolvedAddress = await publicClient.getEnsResolver({ name: normalize(inputValue)}); const resolvedAvatar = await publicClient.getEnsAvatar({ name: normalize(inputValue)}); return { input: inputValue, address: resolvedAddress, avatar: resolvedAvatar, name: inputValue, type: 'name', }}) } else if (addressType === ETH_ADDRESS_TYPE.address) { return await handleResolver(async () => { // const tempName = await provider.lookupAddress(inputValue); const tempName = await publicClient.getEnsName({ address: inputValue}); let tempAvatar; if (tempName) { // tempAvatar = await provider.getAvatar(tempName) tempAvatar = await publicClient.getEnsAvatar({ name: tempName}); } return { input: inputValue, name: tempName, avatar: tempAvatar, address: inputValue, type: 'address', }}) } setAvatarURL(null) throw 'Incorrect address or name' } const isResolveNameNotFound = () => { // console.log("ags resolvedAddress", resolvedAddress); // console.log("ags inputValue", inputValue); // console.log("ags isResolvingInProgress", isResolvingInProgress); return ( !resolvedAddress && inputValue && !isResolvingInProgress && getEthAddressType(inputValue) !== ETH_ADDRESS_TYPE.address ) } const showBlockies = () => { if (props.showBlockies) { let address if (isAddress(inputValue)) { address = inputValue } else if (isAddress(resolvedAddress)) { address = resolvedAddress } if (address) { return ( <SingleNameBlockies address={address.toLowerCase()} imageSize={30} className="blockies" avatarURL={avatarURL} /> ) } } } return ( <div className={`cmp-address-wrapper ${props.className}`}> <div className={`w-full cmp-address ${resolvedAddress ? 'resolved' : ''} ${ error ? 'error' : '' }`} > <div className="input-wrapper"> <div className="indicator"> {isResolvingInProgress && <Loader className="loader" />} {!isResolvingInProgress && showBlockies()} {isResolveNameNotFound() && ( <div alt="warning icon" className="icon-wrapper error-icon mt-2 ml-2" >{<svg width="19" height="17" xmlns="http://www.w3.org/2000/svg"><path d="M9.2456 5.0302c.5001-.1232 1.021.1232 1.2294.5954.0625.1643.1042.3285.1042.5133-.0209.5133-.0625 1.047-.1042 1.5604-.0417.8007-.1042 1.622-.1459 2.4227-.0208.2669-.0208.2874-.0208.5543-.0208.4312-.3542.7597-.7918.7597-.4376 0-.771-.308-.7918-.7391-.0625-1.2525-.1458-2.2996-.2083-3.552-.0209-.3285-.0417-.657-.0625-1.006-.0209-.5133.2917-.965.7917-1.1087m.271 9.4444c-.5835 0-1.0627-.4722-1.0627-1.047 0-.575.4792-1.0472 1.0626-1.0472.5834 0 1.0627.4723 1.0418 1.0677.0209.5543-.4792 1.0265-1.0418 1.0265M2.849 17h13.2936c2.1878 0 3.563-2.3611 2.4795-4.209L11.9544 1.4168c-1.0835-1.889-3.834-1.889-4.9174 0L.3694 12.791C-.6933 14.6594.661 17 2.8489 17" fill="#DC2E2E" fillRule="evenodd"/></svg>} </div> )} {props.DefaultIcon && !inputValue && <DefaultIcon />} </div> <input disabled={props.disabled} value={inputValue} onChange={(e) => handleInput(e.currentTarget.value)} placeholder={props.placeholder} spellCheck={false} name="ethereum" /> </div> <div className="info-wrapper"> {resolvedAddress && <div className="resolved">{resolvedAddress}</div>} </div> </div> </div> ) } Address.propTypes = { provider: PropTypes.object.isRequired, placeholder: PropTypes.string, showBlockies: PropTypes.bool, DefaultIcon: (props, propName) => { if (props[propName] && !isValidElementType(props[propName])) { return new Error( `Invalid prop 'component' supplied to 'Route': the prop is not a valid React component` ) } }, onError: PropTypes.func, onResolve: PropTypes.func, className: PropTypes.string, } Address.defaultProps = { presetValue: '', placeholder: 'Enter Ethereum name or address', showBlockies: true, DefaultIcon: null, className: '', onError: function () {}, onResolve: function () {}, } export default Address