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 @@ -33,6 +33,7 @@
- fixed: Improve the unstake error experience by replacing the popup alert and generic "unknown error occurred" with the real error in the scene's error field, and showing a clear message when the wallet lacks the balance to cover the unstaking network fee.
- fixed: Tapping Max on the Sell scene no longer briefly shows the entered fiat amount in the crypto field while the max is being calculated.
- fixed: An info card no longer disappears into an empty gap when the carousel's card list shrinks. A card's position comes entirely from an animated transform keyed on its index, and that transform is not re-applied when a surviving card shifts slots, so dropping a card left the ones after it parked a full card-width off-screen. The carousel now remounts a card whose slot changes. Reproduces wherever the list shrinks after mount - most visibly when a `noBalance` card is filtered out as balances finish loading.
- fixed: Manage Tokens search now finds a token by its contract address, matching the Assets search.

## 4.50.2 (2026-08-06)

Expand Down
2 changes: 0 additions & 2 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,6 @@ export default [
'src/components/scenes/Loans/LoanManageScene.tsx',
'src/components/scenes/Loans/LoanStatusScene.tsx',

'src/components/scenes/ManageTokensScene.tsx',

'src/components/scenes/NotificationCenterScene.tsx',
'src/components/scenes/NotificationScene.tsx',

Expand Down
83 changes: 82 additions & 1 deletion src/__tests__/walletSearch.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from '@jest/globals'
import type { EdgeToken } from 'edge-core-js'
import type { EdgeToken, EdgeTokenMap } from 'edge-core-js'

import { searchWalletList } from '../components/services/SortedWalletList'
import { filterWalletCreateItemListBySearchText } from '../selectors/getCreateWalletList'
Expand All @@ -14,6 +14,7 @@ import {
testTetherToken,
testWstethToken
} from '../util/fake/fakeSearchTestData'
import { searchTokenIds } from '../util/tokenSearch'

// -----------------------------------------------------------------------------
// searchWalletList Tests
Expand Down Expand Up @@ -503,3 +504,83 @@ describe('Regression: Original search issues', () => {
})
})
})

// -----------------------------------------------------------------------------
// searchTokenIds Tests
// -----------------------------------------------------------------------------

describe('searchTokenIds', () => {
// MOG uses a checksummed contract address, as the token lists do:
const mogToken: EdgeToken = {
currencyCode: 'MOG',
displayName: 'Mog Coin',
denominations: [{ name: 'MOG', multiplier: '100000000000000000' }],
networkLocation: {
contractAddress: '0xaaeE1A9723aaDB7afA2810263653A34bA2C21C7a'
}
}
const mogTokenId = 'aaee1a9723aadb7afa2810263653a34ba2c21c7a'
const tetherTokenId = 'dac17f958d2ee523a2206206994597c13d831ec7'
const wstethTokenId = '7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0'

const allTokens: EdgeTokenMap = {
[mogTokenId]: mogToken,
[tetherTokenId]: testTetherToken,
[wstethTokenId]: testWstethToken
}
const tokenIds = [mogTokenId, tetherTokenId, wstethTokenId]

test('returns all tokenIds when search is empty', () => {
expect(searchTokenIds(allTokens, tokenIds, '')).toEqual(tokenIds)
})

test('matches currency code', () => {
expect(searchTokenIds(allTokens, tokenIds, 'MOG')).toEqual([mogTokenId])
})

test('matches display name', () => {
expect(searchTokenIds(allTokens, tokenIds, 'Tether')).toEqual([
tetherTokenId
])
})

test('matches a full contract address', () => {
expect(
searchTokenIds(
allTokens,
tokenIds,
'0xaaeE1A9723aaDB7afA2810263653A34bA2C21C7a'
)
).toEqual([mogTokenId])
})

test('matches a full contract address in any case', () => {
expect(
searchTokenIds(
allTokens,
tokenIds,
'0xaaee1a9723aadb7afa2810263653a34ba2c21c7a'
)
).toEqual([mogTokenId])
})

test('matches a partial contract address', () => {
expect(searchTokenIds(allTokens, tokenIds, '0xdac17f')).toEqual([
tetherTokenId
])
})

test('returns nothing for an unknown contract address', () => {
expect(
searchTokenIds(
allTokens,
tokenIds,
'0x0000000000000000000000000000000000000000'
)
).toEqual([])
})

test('skips tokenIds missing from the token map', () => {
expect(searchTokenIds(allTokens, ['missing-token-id'], 'mog')).toEqual([])
})
})
15 changes: 5 additions & 10 deletions src/components/scenes/ManageTokensScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { EdgeAppSceneProps } from '../../types/routerTypes'
import type { FlatListItem } from '../../types/types'
import { getWalletName } from '../../util/CurrencyWalletHelpers'
import { logActivity } from '../../util/logger'
import { normalizeForSearch } from '../../util/utils'
import { searchTokenIds } from '../../util/tokenSearch'
import { ButtonsView } from '../buttons/ButtonsView'
import { SceneWrapper } from '../common/SceneWrapper'
import { withWallet } from '../hoc/withWallet'
Expand Down Expand Up @@ -148,15 +148,10 @@ const ManageTokensSceneComponent: React.FC<Props> = props => {
}, [allTokens, sortingBaselineSet])

// Filter the list of tokens based on the search term:
const filteredTokenIds = React.useMemo(() => {
const target = normalizeForSearch(searchValue)
return sortedTokenIds.filter(tokenId => {
const token = allTokens[tokenId]
const currencyCode = normalizeForSearch(token.currencyCode)
const displayName = normalizeForSearch(token.displayName)
return currencyCode.includes(target) || displayName.includes(target)
})
}, [allTokens, searchValue, sortedTokenIds])
const filteredTokenIds = React.useMemo(
() => searchTokenIds(allTokens, sortedTokenIds, searchValue),
[allTokens, searchValue, sortedTokenIds]
)

// Split the list of tokens based on if there were auto-detected tokens given
const autoDetectedTokenIds = React.useMemo(
Expand Down
35 changes: 35 additions & 0 deletions src/util/tokenSearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { EdgeTokenMap } from 'edge-core-js'

import { normalizeForSearch } from './utils'

/**
* Filters a list of tokenIds using a search string.
*
* Asset identification fields (currency code and display name) match anywhere
* in the field, while network location values (ie. contract address) match the
* same way the wallet list and create-wallet list searches do.
*/
export function searchTokenIds(
allTokens: EdgeTokenMap,
tokenIds: string[],
searchText: string
): string[] {
const target = normalizeForSearch(searchText)
if (target === '') return tokenIds

return tokenIds.filter(tokenId => {
const token = allTokens[tokenId]
if (token == null) return false

const { currencyCode, displayName, networkLocation } = token
if (normalizeForSearch(currencyCode).includes(target)) return true
if (normalizeForSearch(displayName).includes(target)) return true

// Search networkLocation values ie. contractAddress:
if (networkLocation == null) return false
return Object.values(networkLocation).some(
value =>
typeof value === 'string' && normalizeForSearch(value).includes(target)
)
})
}
Loading