Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- added: Show swap KYC/terms modal for NExchange
- added: Nym mixnet warning in Stake, Unstake, and Claim Rewards scenes
- added: Resolve and display ZcashNames (.zec) in the Zcash send flow and transaction history.
- changed: Migrate Thorchain Savers and Thorchain Yield endpoints off NineRealms to gateway.liquify.com.

## 4.48.0 (staging)
Expand Down
7 changes: 2 additions & 5 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -288,8 +288,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',
Expand Down Expand Up @@ -360,7 +358,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',
Expand All @@ -374,7 +371,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',
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ module.exports = {
preset: 'react-native',
setupFilesAfterEnv: ['./jestSetup.js'],
transformIgnorePatterns: [
'<rootDir>/node_modules/(?!(@react-native|react-native|@react-navigation))'
'<rootDir>/node_modules/(?!(@react-native|react-native|@react-navigation|zcashname-sdk|@noble/ed25519))'
]
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
16 changes: 16 additions & 0 deletions src/components/modals/AddressModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -183,6 +184,7 @@ export class AddressModalComponent extends React.Component<Props, State> {
return (
this.checkIfUnstoppableDomain(domain) ||
this.checkIfEnsDomain(domain) ||
this.checkIfZnsName(domain) ||
this.checkIfAlias(domain)
)
}
Expand All @@ -193,6 +195,8 @@ export class AddressModalComponent extends React.Component<Props, State> {
checkIfEnsDomain = (name: string): boolean =>
ENS_DOMAINS.some(domain => name.endsWith(domain))

checkIfZnsName = (name: string): boolean => isZnsName(name)

fetchUnstoppableDomainAddress = async (
resolver: Resolver,
domain: string,
Expand Down Expand Up @@ -232,6 +236,13 @@ export class AddressModalComponent extends React.Component<Props, State> {
return address
}

fetchZnsAddress = async (domain: string): Promise<string> => {
const address = await resolveZnsName(domain)
if (address == null)
throw new ResolutionError('UnregisteredDomain', { domain })
return address
}

resolveName = async (name: string, currencyTicker: string): Promise<void> => {
this.setState({ errorLabel: undefined })
if (name === '') return
Expand All @@ -255,6 +266,11 @@ export class AddressModalComponent extends React.Component<Props, State> {
)
} 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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AddressModal drops ZNS name on submit

High Severity

When resolving .zec names, AddressModal returns the resolved Zcash address instead of the original .zec input. This differs from Zano alias handling, causing the original .zec name to be lost and not propagated to spendTarget.otherParams or metadata.name for transaction metadata.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b48db4d. Configure here.

}
if (address == null) {
throw new ResolutionError('UnsupportedDomain', { domain: name })
Expand Down
18 changes: 15 additions & 3 deletions src/components/scenes/SendScene2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ const SendComponent = (props: Props): React.ReactElement => {
const handleChangeAddress =
(spendTarget: EdgeSpendTarget) =>
async (changeAddressResult: ChangeAddressResult): Promise<void> => {
const { addressEntryMethod, parsedUri, fioAddress, alias } =
const { addressEntryMethod, parsedUri, fioAddress, alias, znsName } =
changeAddressResult

if (parsedUri != null) {
Expand Down Expand Up @@ -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
Expand All @@ -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}`
}
Expand Down Expand Up @@ -1320,6 +1323,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) {
Expand Down
8 changes: 7 additions & 1 deletion src/components/scenes/TransactionDetailsScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -444,7 +445,12 @@ export const TransactionDetailsComponent: React.FC<Props> = props => {
direction === 'receive'
? lstrings.transaction_details_sender
: lstrings.transaction_details_recipient
const personName = localMetadata.name ?? personLabel
const recipientAddress = transaction.spendTargets?.[0]?.publicAddress
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
Expand Down
35 changes: 22 additions & 13 deletions src/components/themed/TransactionListRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -57,15 +58,7 @@ interface TransactionViewInnerProps extends TransactionListRowProps {
isCard?: boolean
}

export const TransactionView = (props: TransactionListRowProps) => {
return <TransactionViewInner {...props} />
}

export const TransactionCard = (props: TransactionListRowProps) => {
return <TransactionViewInner {...props} isCard />
}

function TransactionViewInner(props: TransactionViewInnerProps) {
const TransactionViewInner: React.FC<TransactionViewInnerProps> = props => {
const theme = useTheme()
const styles = getStyles(theme)

Expand Down Expand Up @@ -109,7 +102,13 @@ function TransactionViewInner(props: TransactionViewInnerProps) {
account,
wallet
)
const { category, name } = mergedData
const { category, name: metadataName } = mergedData
const recipientAddress = transaction.spendTargets?.[0]?.publicAddress
const znsName = useZnsName(currencyInfo.pluginId, recipientAddress)
const name =
metadataName != null && metadataName !== ''
? metadataName
: znsName ?? metadataName
Comment thread
cursor[bot] marked this conversation as resolved.
const isSentTransaction = direction === 'send'

const cryptoAmount = div(
Expand All @@ -130,7 +129,9 @@ function TransactionViewInner(props: TransactionViewInnerProps) {
)

const cryptoAmountString = `${isSentTransaction ? '-' : '+'}${
denominationSymbol ? denominationSymbol + ' ' : ''
denominationSymbol != null && denominationSymbol !== ''
? denominationSymbol + ' '
: ''
}${cryptoAmountFormat}`

// Fiat Amount
Expand Down Expand Up @@ -249,13 +250,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 ? (
<EdgeCard icon={icon} onPress={handlePress} onLongPress={handleLongPress}>
<SectionView dividerMarginRem={[0.2, 0.5]} marginRem={0.25}>
<>
Expand Down Expand Up @@ -334,6 +335,14 @@ function TransactionViewInner(props: TransactionViewInnerProps) {
)
}

export const TransactionView: React.FC<TransactionListRowProps> = props => {
return <TransactionViewInner {...props} />
}

export const TransactionCard: React.FC<TransactionListRowProps> = props => {
return <TransactionViewInner {...props} isCard />
}

const getStyles = cacheStyles((theme: Theme) => ({
cardlessView: {
flexDirection: 'column',
Expand Down
20 changes: 19 additions & 1 deletion src/components/tiles/AddressTile2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -47,6 +48,7 @@ export interface ChangeAddressResult {
parsedUri?: EdgeParsedUri
addressEntryMethod: AddressEntryMethod
alias?: string
znsName?: string
}

export interface AddressTileRef {
Expand Down Expand Up @@ -150,6 +152,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 {
Expand Down Expand Up @@ -226,6 +229,20 @@ export const AddressTile2 = React.forwardRef(
} catch (_) {}
}

// Preserve and resolve ZcashNames like "alice.zec"
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)
Expand Down Expand Up @@ -270,7 +287,8 @@ export const AddressTile2 = React.forwardRef(
fioAddress,
parsedUri,
addressEntryMethod,
alias: zanoAlias
alias: zanoAlias,
znsName
})
} catch (e: unknown) {
const currencyInfo = coreWallet.currencyInfo
Expand Down
44 changes: 44 additions & 0 deletions src/hooks/useZnsName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useEffect, useState } from 'react'

import { reverseResolveZnsAddress } from '../util/zns'

const cache = new Map<string, string | null>()
const inflight = new Map<string, Promise<string | null>>()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module-level ZNS caches never cleared on logout

Low Severity

The module-level cache and inflight maps in useZnsName.ts and the znsClient singleton in zns.ts persist across logout/login with no exported clear function. Per the module-level-cache-bugs rule, module-level caches must reset on logout/login.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by project rule: Bugbot Review Rules

Reviewed by Cursor Bugbot for commit 0ccd68b. Configure here.


const lookupZnsName = async (address: string): Promise<string | null> => {
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<string | null>(
enabled ? cache.get(address) ?? null : null
)

useEffect(() => {
if (!enabled) return
let cancelled = false
lookupZnsName(address)
.then(result => {
if (!cancelled) setName(result)
})
.catch((_err: unknown) => null)
return () => {
cancelled = true
}
}, [enabled, address])

return name
}
Loading
Loading