diff --git a/CHANGELOG.md b/CHANGELOG.md index a50570a2568..9afeca0ec0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased (develop) +- added: Resolve and display ZcashNames (.zcash) in the Zcash send flow and transaction history. - changed: Remove free FIO handle creation flows. ## 4.49.0 (staging) diff --git a/eslint.config.mjs b/eslint.config.mjs index 5220a1a32fa..4ed016e51dd 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -167,7 +167,7 @@ export default [ 'src/components/layout/Peek.tsx', 'src/components/modals/AccelerateTxModal.tsx', - 'src/components/modals/AddressModal.tsx', + 'src/components/modals/AirshipFullScreenSpinner.tsx', 'src/components/modals/AutoLogoutModal.tsx', 'src/components/modals/BackupModal.tsx', @@ -286,8 +286,6 @@ export default [ 'src/components/scenes/SwapSettingsScene.tsx', 'src/components/scenes/SwapSuccessScene.tsx', - 'src/components/scenes/TransactionDetailsScene.tsx', - 'src/components/scenes/TransactionsExportScene.tsx', 'src/components/scenes/WalletRestoreScene.tsx', @@ -358,7 +356,6 @@ export default [ 'src/components/themed/Thermostat.tsx', 'src/components/themed/Title.tsx', 'src/components/themed/TransactionListComponents.tsx', - 'src/components/themed/TransactionListRow.tsx', 'src/components/themed/VectorIcon.tsx', 'src/components/themed/WalletList.tsx', @@ -372,7 +369,7 @@ export default [ 'src/components/themed/WalletListSwipeableCurrencyRow.tsx', 'src/components/themed/WalletListSwipeableLoadingRow.tsx', - 'src/components/tiles/AddressTile2.tsx', + 'src/components/tiles/AprCard.tsx', 'src/components/tiles/CountdownTile.tsx', 'src/components/tiles/CryptoFiatAmountTile.tsx', diff --git a/jest.config.js b/jest.config.js index 374d6f16b32..b04a05bf1b3 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,6 +11,6 @@ module.exports = { preset: 'react-native', setupFilesAfterEnv: ['./jestSetup.js'], transformIgnorePatterns: [ - '/node_modules/(?!(@react-native|react-native|@react-navigation))' + '/node_modules/(?!(@react-native|react-native|@react-navigation|zcashname-sdk|@noble/ed25519))' ] } diff --git a/package.json b/package.json index b641ba5b9f8..55d0c1be0de 100644 --- a/package.json +++ b/package.json @@ -186,7 +186,8 @@ "url-parse": "^1.5.2", "use-context-selector": "^2.0.0", "yaob": "^0.3.12", - "yavent": "^0.1.5" + "yavent": "^0.1.5", + "zcashname-sdk": "^0.7.2" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/src/actions/LoginActions.tsx b/src/actions/LoginActions.tsx index 1593ae420f2..6429570a7c0 100644 --- a/src/actions/LoginActions.tsx +++ b/src/actions/LoginActions.tsx @@ -19,6 +19,7 @@ import { ConfirmContinueModal } from '../components/modals/ConfirmContinueModal' import { SurveyModal } from '../components/modals/SurveyModal' import { Airship, showError } from '../components/services/AirshipInstance' import { getExperimentConfig } from '../experimentConfig' +import { clearZnsLookupCache } from '../hooks/useZnsName' import { lstrings } from '../locales/strings' import type { WalletCreateItem } from '../selectors/getCreateWalletList' import { config } from '../theme/appConfig' @@ -32,6 +33,7 @@ import { currencyCodesToEdgeAssets } from '../util/CurrencyInfoHelpers' import { logActivity } from '../util/logger' import { logEvent, trackError } from '../util/tracking' import { runWithTimeout } from '../util/utils' +import { resetZnsClient } from '../util/zns' import { loadAccountReferral, refreshAccountReferral @@ -323,6 +325,8 @@ export function logoutRequest( Keyboard.dismiss() Airship.clear() resetLocalAccountSettingsCache() + resetZnsClient() + clearZnsLookupCache() dispatch({ type: 'LOGOUT' }) if (typeof account.logout === 'function') await account.logout() diff --git a/src/components/modals/AddressModal.tsx b/src/components/modals/AddressModal.tsx index c5cb8f877fc..b197212b80d 100644 --- a/src/components/modals/AddressModal.tsx +++ b/src/components/modals/AddressModal.tsx @@ -29,6 +29,7 @@ import { getFioAddressCache } from '../../util/FioAddressUtils' import { resolveName } from '../../util/resolveName' +import { isZnsName, resolveZnsName } from '../../util/zns' import { EdgeButton } from '../buttons/EdgeButton' import { EdgeTouchableWithoutFeedback } from '../common/EdgeTouchableWithoutFeedback' import { showDevError, showError } from '../services/AirshipInstance' @@ -180,9 +181,14 @@ export class AddressModalComponent extends React.Component { } checkIfDomain = (domain: string): boolean => { + // ZNS only resolves on Zcash wallets — gating here so non-zcash wallets + // don't trip into the domain-resolution path on `.zcash` input and end + // up showing a spurious "unsupported domain" error. + const isZcash = this.props.coreWallet.currencyInfo.pluginId === 'zcash' return ( this.checkIfUnstoppableDomain(domain) || this.checkIfEnsDomain(domain) || + (isZcash && this.checkIfZnsName(domain)) || this.checkIfAlias(domain) ) } @@ -193,6 +199,8 @@ export class AddressModalComponent extends React.Component { checkIfEnsDomain = (name: string): boolean => ENS_DOMAINS.some(domain => name.endsWith(domain)) + checkIfZnsName = (name: string): boolean => isZnsName(name) + fetchUnstoppableDomainAddress = async ( resolver: Resolver, domain: string, @@ -232,6 +240,13 @@ export class AddressModalComponent extends React.Component { return address } + fetchZnsAddress = async (domain: string): Promise => { + const address = await resolveZnsName(domain) + if (address == null) + throw new ResolutionError('UnregisteredDomain', { domain }) + return address + } + resolveName = async (name: string, currencyTicker: string): Promise => { this.setState({ errorLabel: undefined }) if (name === '') return @@ -255,6 +270,11 @@ export class AddressModalComponent extends React.Component { ) } else if (this.checkIfEnsDomain(name)) { address = await this.fetchEnsAddress(name) + } else if ( + this.checkIfZnsName(name) && + this.props.coreWallet.currencyInfo.pluginId === 'zcash' + ) { + address = await this.fetchZnsAddress(name) } if (address == null) { throw new ResolutionError('UnsupportedDomain', { domain: name }) @@ -413,6 +433,15 @@ export class AddressModalComponent extends React.Component { ) { submitData = uri } + // Same idea for ZNS (.zcash) names on Zcash — return the original name so + // the caller can capture znsName and persist it in transaction metadata. + if ( + coreWallet.currencyInfo.pluginId === 'zcash' && + typeof uri === 'string' && + isZnsName(uri) + ) { + submitData = uri + } if (errorLabel != null) return this.props.bridge.resolve(submitData) } diff --git a/src/components/scenes/SendScene2.tsx b/src/components/scenes/SendScene2.tsx index f8aa17178b3..6cf4bb68dfe 100644 --- a/src/components/scenes/SendScene2.tsx +++ b/src/components/scenes/SendScene2.tsx @@ -428,7 +428,7 @@ const SendComponent = (props: Props): React.ReactElement => { const handleChangeAddress = (spendTarget: EdgeSpendTarget) => async (changeAddressResult: ChangeAddressResult): Promise => { - const { addressEntryMethod, parsedUri, fioAddress, alias } = + const { addressEntryMethod, parsedUri, fioAddress, alias, znsName } = changeAddressResult if (parsedUri != null) { @@ -468,7 +468,8 @@ const SendComponent = (props: Props): React.ReactElement => { } spendTarget.otherParams = { fioAddress, - zanoAlias: alias + zanoAlias: alias, + znsName } // We can assume the spendTarget object came from the Component spendInfo so simply resetting the spendInfo @@ -495,10 +496,12 @@ const SendComponent = (props: Props): React.ReactElement => { spendTarget: EdgeSpendTarget ): React.ReactElement => { const { publicAddress, nativeAmount, otherParams = {} } = spendTarget - const { fioAddress } = otherParams + const { fioAddress, znsName } = otherParams let title = '' if (fioAddress != null) { title = `Send To (${fioAddress}) ${publicAddress}` + } else if (znsName != null) { + title = `Send To (${znsName}) ${publicAddress}` } else { title = `Send To ${publicAddress}` } @@ -540,7 +543,8 @@ const SendComponent = (props: Props): React.ReactElement => { if (coreWallet != null && hiddenFeaturesMap.address !== true) { // TODO: Change API of AddressTile to access undefined recipientAddress const { publicAddress = '', otherParams = {} } = spendTarget - const { fioAddress } = otherParams + const { fioAddress, zanoAlias, znsName } = otherParams + const recipientName = fioAddress ?? znsName ?? zanoAlias const title = lstrings.send_scene_send_to_address + (spendInfo.spendTargets.length > 1 ? ` ${(index + 1).toString()}` : '') @@ -559,7 +563,7 @@ const SendComponent = (props: Props): React.ReactElement => { resetSendTransaction={handleResetSendTransaction(spendTarget)} lockInputs={lockTilesMap.address} isCameraOpen={doOpenCamera} - fioToAddress={fioAddress} + recipientName={recipientName} navigation={navigation as NavigationBase} /> ) @@ -1320,6 +1324,15 @@ const SendComponent = (props: Props): React.ReactElement => { payeeName = zanoAliases[0] } } + // Same idea for ZNS (.zec) names on Zcash + if (coreWallet.currencyInfo.pluginId === 'zcash') { + const znsNames = spendInfo.spendTargets + .map(t => t.otherParams?.znsName) + .filter((a): a is string => a != null && a.length > 0) + if (znsNames.length === 1) { + payeeName = znsNames[0] + } + } for (const target of spendInfo.spendTargets) { const { fioAddress } = target.otherParams ?? {} if (fioAddress != null) { diff --git a/src/components/scenes/TransactionDetailsScene.tsx b/src/components/scenes/TransactionDetailsScene.tsx index 8add9e8078c..dc36fb94a08 100644 --- a/src/components/scenes/TransactionDetailsScene.tsx +++ b/src/components/scenes/TransactionDetailsScene.tsx @@ -27,6 +27,7 @@ import { useHandler } from '../../hooks/useHandler' import { useHistoricalRate } from '../../hooks/useHistoricalRate' import { useIconColor } from '../../hooks/useIconColor' import { useWatch } from '../../hooks/useWatch' +import { useZnsName } from '../../hooks/useZnsName' import { toPercentString } from '../../locales/intl' import { lstrings } from '../../locales/strings' import { getExchangeDenom } from '../../selectors/DenominationSelectors' @@ -444,7 +445,17 @@ export const TransactionDetailsComponent: React.FC = props => { direction === 'receive' ? lstrings.transaction_details_sender : lstrings.transaction_details_recipient - const personName = localMetadata.name ?? personLabel + // Reverse-lookup only for outgoing txs — `spendTargets[0]` on a receive + // would be our own address. + const recipientAddress = + direction === 'send' + ? transaction.spendTargets?.[0]?.publicAddress + : undefined + const znsName = useZnsName(wallet.currencyInfo.pluginId, recipientAddress) + const personName = + localMetadata.name != null && localMetadata.name !== '' + ? localMetadata.name + : znsName ?? personLabel const personHeader = sprintf( lstrings.transaction_details_person_name, personLabel diff --git a/src/components/themed/TransactionListRow.tsx b/src/components/themed/TransactionListRow.tsx index aa2fdf78798..b74ed61eeb0 100644 --- a/src/components/themed/TransactionListRow.tsx +++ b/src/components/themed/TransactionListRow.tsx @@ -24,6 +24,7 @@ import { useDisplayDenom } from '../../hooks/useDisplayDenom' import { displayFiatAmount } from '../../hooks/useFiatText' import { useHandler } from '../../hooks/useHandler' import { useHistoricalRate } from '../../hooks/useHistoricalRate' +import { useZnsName } from '../../hooks/useZnsName' import { formatNumber } from '../../locales/intl' import { lstrings } from '../../locales/strings' import { getExchangeDenom } from '../../selectors/DenominationSelectors' @@ -57,15 +58,7 @@ interface TransactionViewInnerProps extends TransactionListRowProps { isCard?: boolean } -export const TransactionView = (props: TransactionListRowProps) => { - return -} - -export const TransactionCard = (props: TransactionListRowProps) => { - return -} - -function TransactionViewInner(props: TransactionViewInnerProps) { +const TransactionViewInner: React.FC = props => { const theme = useTheme() const styles = getStyles(theme) @@ -109,7 +102,18 @@ function TransactionViewInner(props: TransactionViewInnerProps) { account, wallet ) - const { category, name } = mergedData + const { category, name: metadataName } = mergedData + // Reverse-lookup only for outgoing txs — `spendTargets[0]` on a receive + // would be our own address. + const recipientAddress = + direction === 'send' + ? transaction.spendTargets?.[0]?.publicAddress + : undefined + const znsName = useZnsName(currencyInfo.pluginId, recipientAddress) + const name = + metadataName != null && metadataName !== '' + ? metadataName + : znsName ?? metadataName const isSentTransaction = direction === 'send' const cryptoAmount = div( @@ -130,7 +134,9 @@ function TransactionViewInner(props: TransactionViewInnerProps) { ) const cryptoAmountString = `${isSentTransaction ? '-' : '+'}${ - denominationSymbol ? denominationSymbol + ' ' : '' + denominationSymbol != null && denominationSymbol !== '' + ? denominationSymbol + ' ' + : '' }${cryptoAmountFormat}` // Fiat Amount @@ -249,13 +255,13 @@ function TransactionViewInner(props: TransactionViewInnerProps) { failOnCancel: false, url } - Share.open(shareOptions).catch(e => { + Share.open(shareOptions).catch((e: unknown) => { showError(e) }) }) // HACK: Handle 100% of the margins because of SceneHeader usage on this scene - return isCard ? ( + return isCard === true ? ( <> @@ -334,6 +340,14 @@ function TransactionViewInner(props: TransactionViewInnerProps) { ) } +export const TransactionView: React.FC = props => { + return +} + +export const TransactionCard: React.FC = props => { + return +} + const getStyles = cacheStyles((theme: Theme) => ({ cardlessView: { flexDirection: 'column', diff --git a/src/components/tiles/AddressTile2.tsx b/src/components/tiles/AddressTile2.tsx index a2bed4b8d6b..a738786a748 100644 --- a/src/components/tiles/AddressTile2.tsx +++ b/src/components/tiles/AddressTile2.tsx @@ -25,6 +25,7 @@ import { parseDeepLink } from '../../util/DeepLinkParser' import { checkPubAddress } from '../../util/FioAddressUtils' import { resolveName } from '../../util/resolveName' import { isEmail } from '../../util/utils' +import { isZnsName, resolveZnsName } from '../../util/zns' import { EdgeAnim } from '../common/EdgeAnim' import { EdgeTouchableOpacity } from '../common/EdgeTouchableOpacity' import { AddressModal } from '../modals/AddressModal' @@ -47,6 +48,7 @@ export interface ChangeAddressResult { parsedUri?: EdgeParsedUri addressEntryMethod: AddressEntryMethod alias?: string + znsName?: string } export interface AddressTileRef { @@ -62,7 +64,11 @@ interface Props { resetSendTransaction: () => void lockInputs?: boolean isCameraOpen: boolean - fioToAddress?: string + /** + * Friendly recipient name to render above the public address — e.g. a FIO + * handle, Zano alias, or ZNS (.zcash) name. Display-only. + */ + recipientName?: string navigation: NavigationBase } @@ -71,7 +77,7 @@ export const AddressTile2 = React.forwardRef( const { coreWallet, tokenId, - fioToAddress, + recipientName, isCameraOpen, lockInputs, navigation, @@ -150,6 +156,7 @@ export const AddressTile2 = React.forwardRef( const enteredInput = address.trim() address = enteredInput let zanoAlias: string | undefined + let znsName: string | undefined let fioAddress if (fioPlugin != null) { try { @@ -226,6 +233,20 @@ export const AddressTile2 = React.forwardRef( } catch (_) {} } + // Preserve and resolve ZcashNames like "alice.zcash" + if ( + coreWallet.currencyInfo.pluginId === 'zcash' && + isZnsName(enteredInput) + ) { + try { + const resolved = await resolveZnsName(enteredInput) + if (resolved != null) { + znsName = enteredInput.toLowerCase() + address = resolved + } + } catch (_) {} + } + try { const parsedUri: EdgeParsedUri & { paymentProtocolUrl?: string } = await coreWallet.parseUri(address, currencyCode) @@ -270,7 +291,8 @@ export const AddressTile2 = React.forwardRef( fioAddress, parsedUri, addressEntryMethod, - alias: zanoAlias + alias: zanoAlias, + znsName }) } catch (e: unknown) { const currencyInfo = coreWallet.currencyInfo @@ -341,12 +363,25 @@ export const AddressTile2 = React.forwardRef( }) const handleChangeAddress = useHandler(async () => { + const nameServices: string[] = [] + if (fioPlugin != null) nameServices.push('FIO') + if (coreWallet.currencyInfo.pluginId === 'ethereum') + nameServices.push('ENS') + if (coreWallet.currencyInfo.pluginId === 'zcash') nameServices.push('ZNS') + const title = + nameServices.length > 0 + ? sprintf( + lstrings.scan_address_modal_title_1s, + nameServices.join(', ') + ) + : lstrings.scan_address_modal_title + Airship.show(bridge => ( )) .then(async result => { @@ -493,8 +528,8 @@ export const AddressTile2 = React.forwardRef( enter={{ type: 'stretchInY' }} exit={{ type: 'stretchOutY' }} > - {fioToAddress == null ? null : ( - {fioToAddress + '\n'} + {recipientName == null ? null : ( + {recipientName + '\n'} )} () +const inflight = new Map>() + +export const clearZnsLookupCache = (): void => { + cache.clear() + inflight.clear() +} + +const lookupZnsName = async (address: string): Promise => { + if (cache.has(address)) return cache.get(address) ?? null + let promise = inflight.get(address) + if (promise == null) { + promise = reverseResolveZnsAddress(address).catch((_err: unknown) => null) + inflight.set(address, promise) + } + const result = await promise + cache.set(address, result) + inflight.delete(address) + return result +} + +export const useZnsName = ( + pluginId: string, + address: string | undefined +): string | null => { + const enabled = pluginId === 'zcash' && address != null && address !== '' + const [name, setName] = useState( + enabled ? cache.get(address) ?? null : null + ) + + useEffect(() => { + if (!enabled) { + // Clear stale name when the hook is disabled (e.g. component recycled + // onto a non-zcash row, or address became undefined). + setName(null) + return + } + // Reset to the current cache value (or null) immediately on address + // change so a recycled component doesn't briefly show the prior row's + // resolved name while the async lookup is in flight. + setName(cache.get(address) ?? null) + let cancelled = false + lookupZnsName(address) + .then(result => { + if (!cancelled) setName(result) + }) + .catch((_err: unknown) => null) + return () => { + cancelled = true + } + }, [enabled, address]) + + return name +} diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index d451fd23a61..6b289adf405 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -1368,7 +1368,8 @@ const strings = { personalize_wallet_title: 'Personalize Your Wallet', get_started_button: 'Get Started', not_now_button: 'Not Now', - scan_address_modal_title: 'Enter Recipient FIO, ENS, or Public Address', + scan_address_modal_title: 'Enter Recipient Public Address', + scan_address_modal_title_1s: 'Enter Recipient %1$s or Public Address', enter_any_title: 'Enter any of the following:', enter_any_body: '1. Public address to send money to\n2. Private key to sweep\n3. Wallet Connect URI to connect to a dApp\n4. Edge Login URI to login to another device\n\nEdge will auto-detect the URI format and allow you to select an appropriate wallet', diff --git a/src/locales/strings/de.json b/src/locales/strings/de.json index 5e05c65d412..ee4ba22ad62 100644 --- a/src/locales/strings/de.json +++ b/src/locales/strings/de.json @@ -1086,7 +1086,8 @@ "personalize_wallet_title": "Personalize Your Wallet", "get_started_button": "Get Started", "not_now_button": "Not Now", - "scan_address_modal_title": "Empfänger FIO, ENS oder öffentliche Adresse eingeben", + "scan_address_modal_title": "Öffentliche Adresse des Empfängers eingeben", + "scan_address_modal_title_1s": "Empfänger %1$s oder öffentliche Adresse eingeben", "enter_any_title": "Enter any of the following:", "enter_any_body": "1. Public address to send money to\n2. Private key to sweep\n3. Wallet Connect URI to connect to a dApp\n4. Edge Login URI to login to another device\n\nEdge will auto-detect the URI format and allow you to select an appropriate wallet", "enter_any_input_hint": "Enter any of the above", diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json index 584b22395b4..deedd2840cd 100644 --- a/src/locales/strings/enUS.json +++ b/src/locales/strings/enUS.json @@ -1086,7 +1086,8 @@ "personalize_wallet_title": "Personalize Your Wallet", "get_started_button": "Get Started", "not_now_button": "Not Now", - "scan_address_modal_title": "Enter Recipient FIO, ENS, or Public Address", + "scan_address_modal_title": "Enter Recipient Public Address", + "scan_address_modal_title_1s": "Enter Recipient %1$s or Public Address", "enter_any_title": "Enter any of the following:", "enter_any_body": "1. Public address to send money to\n2. Private key to sweep\n3. Wallet Connect URI to connect to a dApp\n4. Edge Login URI to login to another device\n\nEdge will auto-detect the URI format and allow you to select an appropriate wallet", "enter_any_input_hint": "Enter any of the above", diff --git a/src/locales/strings/es.json b/src/locales/strings/es.json index 06ed6c81b2c..27e780a98c5 100644 --- a/src/locales/strings/es.json +++ b/src/locales/strings/es.json @@ -1086,7 +1086,8 @@ "personalize_wallet_title": "Personalize Your Wallet", "get_started_button": "Get Started", "not_now_button": "Not Now", - "scan_address_modal_title": "Introduce el destinatario FIO, ENS, o dirección pública", + "scan_address_modal_title": "Introduce la dirección pública del destinatario", + "scan_address_modal_title_1s": "Introduce el destinatario %1$s o dirección pública", "enter_any_title": "Enter any of the following:", "enter_any_body": "1. Public address to send money to\n2. Private key to sweep\n3. Wallet Connect URI to connect to a dApp\n4. Edge Login URI to login to another device\n\nEdge will auto-detect the URI format and allow you to select an appropriate wallet", "enter_any_input_hint": "Enter any of the above", diff --git a/src/locales/strings/esMX.json b/src/locales/strings/esMX.json index 052f5bbe16d..1bb99b7b4b5 100644 --- a/src/locales/strings/esMX.json +++ b/src/locales/strings/esMX.json @@ -1086,7 +1086,8 @@ "personalize_wallet_title": "Personaliza tu billetera", "get_started_button": "Comenzar", "not_now_button": "Ahora no", - "scan_address_modal_title": "Introduce el destinatario FIO, ENS, o dirección pública", + "scan_address_modal_title": "Introduce la dirección pública del destinatario", + "scan_address_modal_title_1s": "Introduce el destinatario %1$s o dirección pública", "enter_any_title": "Ingresa cualquiera de los siguientes:", "enter_any_body": "1. Dirección pública para enviar dinero\n2. Clave privada para barrer fondos\n3. URI de Wallet Connect para conectar con una dApp\n4. URI de Edge Login para iniciar sesión en otro dispositivo\n\nEdge detectará automáticamente el formato de la URI y te permitirá seleccionar una billetera adecuada", "enter_any_input_hint": "Ingresa cualquiera de los anteriores", diff --git a/src/locales/strings/fr.json b/src/locales/strings/fr.json index a3b44137e07..adc6a34b860 100644 --- a/src/locales/strings/fr.json +++ b/src/locales/strings/fr.json @@ -1086,7 +1086,8 @@ "personalize_wallet_title": "Personalize Your Wallet", "get_started_button": "Get Started", "not_now_button": "Not Now", - "scan_address_modal_title": "Enter Recipient FIO, ENS, or Public Address", + "scan_address_modal_title": "Enter Recipient Public Address", + "scan_address_modal_title_1s": "Enter Recipient %1$s or Public Address", "enter_any_title": "Enter any of the following:", "enter_any_body": "1. Public address to send money to\n2. Private key to sweep\n3. Wallet Connect URI to connect to a dApp\n4. Edge Login URI to login to another device\n\nEdge will auto-detect the URI format and allow you to select an appropriate wallet", "enter_any_input_hint": "Enter any of the above", diff --git a/src/locales/strings/it.json b/src/locales/strings/it.json index d1cfedaef7e..e0f646a1ca7 100644 --- a/src/locales/strings/it.json +++ b/src/locales/strings/it.json @@ -1086,7 +1086,8 @@ "personalize_wallet_title": "Personalizza Il Tuo Portafoglio", "get_started_button": "Inizia", "not_now_button": "Non ora", - "scan_address_modal_title": "Inserisci indirizzo FIO, ENS o indirizzo pubblico del destinatario", + "scan_address_modal_title": "Inserisci l'indirizzo pubblico del destinatario", + "scan_address_modal_title_1s": "Inserisci %1$s o l'indirizzo pubblico del destinatario", "enter_any_title": "Inserisci uno dei seguenti elementi:", "enter_any_body": "1. Indirizzo pubblico a cui inviare denaro\n2. Chiave privata per importare i fondi \n3. URI di Wallet Connect per connetterti a una dApp\n4. Edge Login URI per accedere su un altro dispositivo\n\nEdge rileverà automaticamente il formato URI e ti consentirà di selezionare un portafoglio appropriato", "enter_any_input_hint": "Inserisci uno qualsiasi dei precedenti", diff --git a/src/locales/strings/ja.json b/src/locales/strings/ja.json index be50373ecf5..fef46367aad 100644 --- a/src/locales/strings/ja.json +++ b/src/locales/strings/ja.json @@ -1086,7 +1086,8 @@ "personalize_wallet_title": "ハンドル名を決めてください", "get_started_button": "始める", "not_now_button": "後で", - "scan_address_modal_title": "受信者のFIO、ENS、または公開アドレスを入力してください", + "scan_address_modal_title": "受信者の公開アドレスを入力してください", + "scan_address_modal_title_1s": "受信者の%1$sまたは公開アドレスを入力してください", "enter_any_title": "次のいずれかを入力します:", "enter_any_body": "1. 送金先の公開アドレス\n2. スイープ用の秘密鍵\n3. dAppに接続するためのWallet Connect URI\n4. 別のデバイスにログインするためのEdge Login URI\n\nEdgeはURIの形式を自動で検出するため、適切なウォレットを選択することができます。", "enter_any_input_hint": "上記のいずれかを入力してください", diff --git a/src/locales/strings/kaa.json b/src/locales/strings/kaa.json index dd63a06a38b..0c69ea551da 100644 --- a/src/locales/strings/kaa.json +++ b/src/locales/strings/kaa.json @@ -1086,7 +1086,8 @@ "personalize_wallet_title": "Personalize Your Wallet", "get_started_button": "Get Started", "not_now_button": "Not Now", - "scan_address_modal_title": "Enter Recipient FIO, ENS, or Public Address", + "scan_address_modal_title": "Enter Recipient Public Address", + "scan_address_modal_title_1s": "Enter Recipient %1$s or Public Address", "enter_any_title": "Enter any of the following:", "enter_any_body": "1. Public address to send money to\n2. Private key to sweep\n3. Wallet Connect URI to connect to a dApp\n4. Edge Login URI to login to another device\n\nEdge will auto-detect the URI format and allow you to select an appropriate wallet", "enter_any_input_hint": "Enter any of the above", diff --git a/src/locales/strings/ko.json b/src/locales/strings/ko.json index 8619ef0c6ef..76ea6b2b0bf 100644 --- a/src/locales/strings/ko.json +++ b/src/locales/strings/ko.json @@ -1086,7 +1086,8 @@ "personalize_wallet_title": "Personalize Your Wallet", "get_started_button": "Get Started", "not_now_button": "Not Now", - "scan_address_modal_title": "Enter Recipient FIO, ENS, or Public Address", + "scan_address_modal_title": "Enter Recipient Public Address", + "scan_address_modal_title_1s": "Enter Recipient %1$s or Public Address", "enter_any_title": "Enter any of the following:", "enter_any_body": "1. Public address to send money to\n2. Private key to sweep\n3. Wallet Connect URI to connect to a dApp\n4. Edge Login URI to login to another device\n\nEdge will auto-detect the URI format and allow you to select an appropriate wallet", "enter_any_input_hint": "Enter any of the above", diff --git a/src/locales/strings/pt.json b/src/locales/strings/pt.json index cd5ab747be8..c427287b0c1 100644 --- a/src/locales/strings/pt.json +++ b/src/locales/strings/pt.json @@ -1086,7 +1086,8 @@ "personalize_wallet_title": "Personalize Sua Carteira", "get_started_button": "Começar", "not_now_button": "Agora não", - "scan_address_modal_title": "Digite o destinatário FIO, ENS ou Endereço Público", + "scan_address_modal_title": "Digite o endereço público do destinatário", + "scan_address_modal_title_1s": "Digite o %1$s ou endereço público do destinatário", "enter_any_title": "Digite qualquer um dos seguintes:", "enter_any_body": "1. Public address para enviar dinheiro\n2. Private key para varrer\n3. Wallet Connect URI para se conectar a um dApp\n4. Edge Login URI para fazer login em outro dispositivo\n\nA Edge detectará automaticamente o formato URI e permitirá que você selecione uma carteira apropriada", "enter_any_input_hint": "Digite qualquer um dos itens acima", diff --git a/src/locales/strings/ru.json b/src/locales/strings/ru.json index ee728acf965..e31e18302cd 100644 --- a/src/locales/strings/ru.json +++ b/src/locales/strings/ru.json @@ -1086,7 +1086,8 @@ "personalize_wallet_title": "Personalize Your Wallet", "get_started_button": "Get Started", "not_now_button": "Not Now", - "scan_address_modal_title": "Введите FIO, ENS или публичный адрес получателя", + "scan_address_modal_title": "Введите публичный адрес получателя", + "scan_address_modal_title_1s": "Введите %1$s или публичный адрес получателя", "enter_any_title": "Enter any of the following:", "enter_any_body": "1. Public address to send money to\n2. Private key to sweep\n3. Wallet Connect URI to connect to a dApp\n4. Edge Login URI to login to another device\n\nEdge will auto-detect the URI format and allow you to select an appropriate wallet", "enter_any_input_hint": "Enter any of the above", diff --git a/src/locales/strings/vi.json b/src/locales/strings/vi.json index 690f127d552..7c75d3232d2 100644 --- a/src/locales/strings/vi.json +++ b/src/locales/strings/vi.json @@ -1086,7 +1086,8 @@ "personalize_wallet_title": "Personalize Your Wallet", "get_started_button": "Get Started", "not_now_button": "Not Now", - "scan_address_modal_title": "Enter Recipient FIO, ENS, or Public Address", + "scan_address_modal_title": "Enter Recipient Public Address", + "scan_address_modal_title_1s": "Enter Recipient %1$s or Public Address", "enter_any_title": "Enter any of the following:", "enter_any_body": "1. Public address to send money to\n2. Private key to sweep\n3. Wallet Connect URI to connect to a dApp\n4. Edge Login URI to login to another device\n\nEdge will auto-detect the URI format and allow you to select an appropriate wallet", "enter_any_input_hint": "Enter any of the above", diff --git a/src/locales/strings/zh.json b/src/locales/strings/zh.json index 999096147e5..86168d73c17 100644 --- a/src/locales/strings/zh.json +++ b/src/locales/strings/zh.json @@ -1086,7 +1086,8 @@ "personalize_wallet_title": "Personalize Your Wallet", "get_started_button": "Get Started", "not_now_button": "Not Now", - "scan_address_modal_title": "Enter Recipient FIO, ENS, or Public Address", + "scan_address_modal_title": "Enter Recipient Public Address", + "scan_address_modal_title_1s": "Enter Recipient %1$s or Public Address", "enter_any_title": "Enter any of the following:", "enter_any_body": "1. Public address to send money to\n2. Private key to sweep\n3. Wallet Connect URI to connect to a dApp\n4. Edge Login URI to login to another device\n\nEdge will auto-detect the URI format and allow you to select an appropriate wallet", "enter_any_input_hint": "Enter any of the above", diff --git a/src/util/zns.ts b/src/util/zns.ts new file mode 100644 index 00000000000..ae364d05ec6 --- /dev/null +++ b/src/util/zns.ts @@ -0,0 +1,40 @@ +import { ZNS } from 'zcashname-sdk' + +export const ZNS_SUFFIX = '.zcash' + +// The SDK's `network` option only selects the registry address; its `url` +// always falls back to the testnet indexer unless overridden explicitly. +const ZNS_MAINNET_URL = 'https://main.zcashnames.com' + +let znsClient: ZNS | null = null + +const getZns = (): ZNS => { + znsClient ??= new ZNS({ network: 'mainnet', url: ZNS_MAINNET_URL }) + return znsClient +} + +export const resetZnsClient = (): void => { + znsClient = null +} + +export const isZnsName = (input: string): boolean => + input.toLowerCase().endsWith(ZNS_SUFFIX) + +export const stripZnsSuffix = (input: string): string => { + const lower = input.toLowerCase() + return lower.endsWith(ZNS_SUFFIX) + ? lower.slice(0, lower.length - ZNS_SUFFIX.length) + : lower +} + +export const resolveZnsName = async (input: string): Promise => { + const reg = await getZns().resolveName(stripZnsSuffix(input)) + return reg?.address ?? null +} + +export const reverseResolveZnsAddress = async ( + address: string +): Promise => { + const regs = await getZns().resolveAddress(address, 1, 0) + return regs.length > 0 ? `${regs[0].name}${ZNS_SUFFIX}` : null +} diff --git a/yarn.lock b/yarn.lock index 30c3ae6ab09..ec853249fa7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3416,6 +3416,11 @@ dependencies: "@noble/hashes" "1.7.1" +"@noble/ed25519@^2.0.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-2.3.0.tgz#df0dbe424cfd7da4968b956b819c15db5fbe7f21" + integrity sha512-M7dvXL2B92/M7dw9+gzuydL8qn/jiqNHaoR3Q+cb1q1GHV7uwE17WCyFMG+Y+TZb5izcaXk5TdJRrDUxHXL78A== + "@noble/hashes@1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" @@ -18960,6 +18965,14 @@ yoctocolors-cjs@^2.1.2: resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242" integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA== +zcashname-sdk@^0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/zcashname-sdk/-/zcashname-sdk-0.7.2.tgz#472d4265dd0b9778f8cb327b8f0382f89229ca2f" + integrity sha512-VVq9LHNRgpFq0+Qzxqd7bRmk+KAt3uzJW2lthKeqsgSedRjjhML9Y+IBhRYyh/gge8rFOiKWdx3EyM9LJkwqAg== + dependencies: + "@noble/ed25519" "^2.0.0" + bech32 "^2.0.0" + zod@^3.21.4: version "3.23.8" resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"