From e846a09378145ed3dc7f41adb7394826195b57af Mon Sep 17 00:00:00 2001 From: Cryptomiester <77497858+crypt0miester@users.noreply.github.com> Date: Wed, 9 Aug 2023 07:43:01 +0000 Subject: [PATCH] integrate alldomains to helium wallet: * add TldParser to retrieve domain owner * add debounce to avoid unneccessary rpc calls * add alias/nickname as the domain when applicable --- package.json | 21 +++++++------- src/features/addressBook/ContactDetails.tsx | 30 ++++++++++++++++++++ src/features/payment/PaymentItem.tsx | 15 +++++----- src/features/payment/PaymentScreen.tsx | 31 ++++++++++++++++++--- src/utils/getDomainOwner.ts | 16 +++++++++++ yarn.lock | 8 ++++++ 6 files changed, 99 insertions(+), 22 deletions(-) create mode 100644 src/utils/getDomainOwner.ts diff --git a/package.json b/package.json index 7347870c3..56a79ff35 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "@ledgerhq/react-native-hw-transport-ble": "6.27.2", "@metaplex-foundation/js": "0.17.6", "@metaplex-foundation/mpl-bubblegum": "0.6.0", + "@onsol/tldparser": "^0.5.3", "@pythnetwork/client": "^2.17.0", "@react-native-async-storage/async-storage": "1.18.1", "@react-native-community/blur": "4.3.0", @@ -289,29 +290,29 @@ "tls": false }, "browser": { - "zlib": "browserify-zlib", + "_stream_duplex": "readable-stream/duplex", + "_stream_passthrough": "readable-stream/passthrough", + "_stream_readable": "readable-stream/readable", + "_stream_transform": "readable-stream/transform", + "_stream_writable": "readable-stream/writable", "console": "console-browserify", "constants": "constants-browserify", "crypto": "react-native-crypto", + "dgram": "react-native-udp", "dns": "dns.js", - "net": "react-native-tcp", "domain": "domain-browser", + "fs": "react-native-level-fs", "http": "@tradle/react-native-http", "https": "https-browserify", + "net": "react-native-tcp", "os": "react-native-os", "path": "path-browserify", "querystring": "querystring-es3", - "fs": "react-native-level-fs", - "_stream_transform": "readable-stream/transform", - "_stream_readable": "readable-stream/readable", - "_stream_writable": "readable-stream/writable", - "_stream_duplex": "readable-stream/duplex", - "_stream_passthrough": "readable-stream/passthrough", - "dgram": "react-native-udp", "stream": "stream-browserify", "timers": "timers-browserify", + "tls": false, "tty": "tty-browserify", "vm": "vm-browserify", - "tls": false + "zlib": "browserify-zlib" } } diff --git a/src/features/addressBook/ContactDetails.tsx b/src/features/addressBook/ContactDetails.tsx index 8471fe956..f84ef9026 100644 --- a/src/features/addressBook/ContactDetails.tsx +++ b/src/features/addressBook/ContactDetails.tsx @@ -28,6 +28,8 @@ import useAlert from '@hooks/useAlert' import CloseButton from '@components/CloseButton' import { solAddressIsValid, accountNetType } from '@utils/accountUtils' import { heliumAddressFromSolAddress } from '@helium/spl-utils' +import { useDebounce } from 'use-debounce' +import { fetchDomainOwner } from '@utils/getDomainOwner' import { HomeNavigationProp } from '../home/homeTypes' import { useAccountStorage } from '../../storage/AccountStorageProvider' import { @@ -37,6 +39,7 @@ import { import { useAppStorage } from '../../storage/AppStorageProvider' import AddressExtra from './AddressExtra' import { CSAccount } from '../../storage/cloudStorage' +import { useSolana } from '../../solana/SolanaProvider' const BUTTON_HEIGHT = 55 @@ -62,6 +65,9 @@ const ContactDetails = ({ action, contact }: Props) => { const { scannedAddress, setScannedAddress } = useAppStorage() const spacing = useSpacing() const { showOKCancelAlert } = useAlert() + const { connection } = useSolana() + // debounce is needed to avoid unneccessary rpc calls + const [debouncedAddress] = useDebounce(address, 800) useEffect(() => { if (route.params?.address) { @@ -71,6 +77,30 @@ const ContactDetails = ({ action, contact }: Props) => { } }, [contact, route]) + const handleDomainAddress = useCallback( + async ({ domain }: { domain: string }) => { + if (!connection) return + return fetchDomainOwner(connection, domain) + }, + [connection], + ) + + useEffect(() => { + // only parse addresses which include dots. + if (debouncedAddress.split('.').length === 2) { + handleDomainAddress({ domain: debouncedAddress }).then( + (resolvedAddress) => { + // owner was not found so we do not set the owner address + if (!resolvedAddress) return + setAddress(resolvedAddress) + // if nickname was previously set we ignore setting the domain as nickname + if (nickname) return + setNickname(debouncedAddress) + }, + ) + } + }, [debouncedAddress, handleDomainAddress, nickname]) + const onRequestClose = useCallback(() => { homeNav.goBack() }, [homeNav]) diff --git a/src/features/payment/PaymentItem.tsx b/src/features/payment/PaymentItem.tsx index e7aa6af3f..cd24fb8ec 100644 --- a/src/features/payment/PaymentItem.tsx +++ b/src/features/payment/PaymentItem.tsx @@ -26,6 +26,7 @@ import TextInput from '@components/TextInput' import AccountIcon from '@components/AccountIcon' import BackgroundFill from '@components/BackgroundFill' import { Theme } from '@theme/theme' +import { useDebouncedCallback } from 'use-debounce' import { CSAccount } from '../../storage/cloudStorage' import { balanceToString, useBalance } from '../../utils/Balance' import { accountNetType, ellipsizeAddress } from '../../utils/accountUtils' @@ -123,14 +124,12 @@ const PaymentItem = ({ onToggleMax({ address, index }) }, [address, index, onToggleMax]) - const handleEditAddress = useCallback( - (text?: string) => { - onEditAddress({ address: text || '', index }) - }, - [index, onEditAddress], - ) + // debounce is needed to avoid unneccessary rpc calls + const handleEditAddress = useDebouncedCallback((text?: string) => { + onEditAddress({ address: text || '', index }) + }, 800) - const handleAddressBlur = useCallback( + const handleAddressBlur = useDebouncedCallback( (event?: NativeSyntheticEvent) => { const text = event?.nativeEvent.text handleAddressError({ @@ -139,7 +138,7 @@ const PaymentItem = ({ isHotspotOrValidator: false, }) }, - [handleAddressError, index], + 800, ) const handleRemove = useCallback(() => { diff --git a/src/features/payment/PaymentScreen.tsx b/src/features/payment/PaymentScreen.tsx index 1a557e556..9469d01e6 100644 --- a/src/features/payment/PaymentScreen.tsx +++ b/src/features/payment/PaymentScreen.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-param-reassign */ import React, { useCallback, useState, @@ -50,6 +51,7 @@ import TokenMOBILE from '@assets/images/tokenMOBILE.svg' import { calcCreateAssociatedTokenAccountAccountFee } from '@utils/solanaUtils' import { Mints } from '@utils/constants' import { PublicKey } from '@solana/web3.js' +import { fetchDomainOwner } from '@utils/getDomainOwner' import { useSolana } from '../../solana/SolanaProvider' import { HomeNavigationProp, @@ -106,7 +108,7 @@ const PaymentScreen = () => { const hntKeyboardRef = useRef(null) const { oraclePrice, hntBalance, solBalance, iotBalance, mobileBalance } = useBalance() - const { anchorProvider } = useSolana() + const { anchorProvider, connection } = useSolana() const appDispatch = useAppDispatch() const navigation = useNavigation() @@ -491,6 +493,14 @@ const PaymentScreen = () => { [dispatch], ) + const handleDomainAddress = useCallback( + async ({ domain }: { domain: string }) => { + if (!connection) return + return fetchDomainOwner(connection, domain) + }, + [connection], + ) + const handleAddressError = useCallback( ({ index, @@ -507,6 +517,11 @@ const PaymentScreen = () => { } let invalidAddress = false + // only handle address which include dots. + if (address.split('.').length === 2) { + // retrieve the address which has been set previously by handleEditAddress. + address = paymentState.payments[index].address || '' + } invalidAddress = !!address && !solAddressIsValid(address) const wrongNetType = @@ -515,20 +530,27 @@ const PaymentScreen = () => { accountNetType(address) !== networkType handleSetPaymentError(index, invalidAddress || wrongNetType) }, - [handleSetPaymentError, networkType], + [handleSetPaymentError, networkType, paymentState], ) const handleEditAddress = useCallback( async ({ index, address }: { index: number; address: string }) => { if (index === undefined || !currentAccount || !anchorProvider) return - + let domain = '' + if (address.split('.').length === 2) { + const resolvedAddress = + (await handleDomainAddress({ domain: address })) || '' + address = resolvedAddress + // if the address is resolved then the domain could also be an alias/nickname of the address. + if (resolvedAddress) domain = address + } const allAccounts = unionBy( contacts, Object.values(accounts || {}), ({ address: addr }) => addr, ) let contact = allAccounts.find((c) => c.address === address) - if (!contact) contact = { address, netType: networkType, alias: '' } + if (!contact) contact = { address, netType: networkType, alias: domain } const createTokenAccountFee = await calcCreateAssociatedTokenAccountAccountFee( @@ -553,6 +575,7 @@ const PaymentScreen = () => { networkType, anchorProvider, mint, + handleDomainAddress, ], ) diff --git a/src/utils/getDomainOwner.ts b/src/utils/getDomainOwner.ts new file mode 100644 index 000000000..305584ba9 --- /dev/null +++ b/src/utils/getDomainOwner.ts @@ -0,0 +1,16 @@ +import { TldParser } from '@onsol/tldparser' +import { Connection } from '@solana/web3.js' + +// retrives AllDomain domain owner. +// the domain must include the dot +export async function fetchDomainOwner(connection: Connection, domain: string) { + try { + const parser = new TldParser(connection) + const owner = await parser.getOwnerFromDomainTld(domain) + if (!owner) return + return owner.toBase58() + } catch (e) { + // Handle the error here if needed + // console.error("Error fetching domain owner:", e); + } +} diff --git a/yarn.lock b/yarn.lock index d9d8983c5..c605ba709 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3327,6 +3327,14 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@onsol/tldparser@^0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@onsol/tldparser/-/tldparser-0.5.3.tgz#f5a0a06fa69af0e8a2783464bbd32b3e88ad0b1a" + integrity sha512-rICUDhYPwDuO81wo4HI7QSCf6kQiaM0mSv3HKBJPrRxliIvgwanAoU5H0p54HEdAKeS3pmeLi5wB6ROpGxTZ/A== + dependencies: + "@ethersproject/sha2" "^5.7.0" + "@metaplex-foundation/beet-solana" "^0.4.0" + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"