Skip to content

Commit

Permalink
[Release] New release 2.28.0 (minor) (#11909)
Browse files Browse the repository at this point in the history
* chore: bump version to v2.28.0

* fix: sort tokens in token picker (#11911)

* fix: sort tokens in token picker

* fix: mf-6477 do not show swap button for unsupported chain

* fixup! fix: sort tokens in token picker

* fix: mf-6482 merge all lens and fids (#11912)

* fix: mf-6481 some feeds might render empty

* fix: mf-6482 merge all lens and fids

* fix: mf-6483 avatar from API could be outdate (#11913)

* fix(Web3Profile): mf-6484 initial pending config (#11914)

* fix: mf-6486 mf-6487 trader ui issues (#11915)

* fix: NaN close MF-6478

* fix: follow up owner reviews (#11916)

* fix: specific token picker title for trader

* fix: do not pass toToken for native token

* fix: network fee

* fix: network fee

* fix: run codegen

---------

Co-authored-by: swkatmask <[email protected]>

* feat: add add search box for token picker (#11919)

* feat: add add search box for token picker

* fix: add metis name

* fixup! feat: add add search box for token picker

* fix: guarantee persona proofs (#11921)

closes #11920

---------

Co-authored-by: Wukong Sun <[email protected]>
Co-authored-by: Jack Works <[email protected]>
Co-authored-by: swkatmask <[email protected]>
  • Loading branch information
4 people authored Nov 14, 2024
1 parent 9481c1b commit 985e74a
Show file tree
Hide file tree
Showing 45 changed files with 423 additions and 122 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"yarn": ">=999.0.0",
"npm": ">=999.0.0"
},
"version": "2.27.1",
"version": "2.28.0",
"private": true,
"license": "AGPL-3.0-or-later",
"scripts": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ function resolveCurrentVisitingIdentityInner(
const handle = legacy.screen_name
const ownerHandle = ownerRef.value.identifier?.userId
const isOwner = !!ownerHandle && handle.toLowerCase() === ownerHandle.toLowerCase()
const avatar = legacy.profile_image_url_https
const domAvatar = document.querySelector(`a[href="/${handle}/photo"] img`)
// DOM avatar is more accurate, avatar from api could be outdate
const avatar = domAvatar?.getAttribute('src') || legacy.profile_image_url_https
const bio = legacy.profile_image_url_https
const homepage = legacy.entities.url?.urls?.[0]?.expanded_url

Expand Down
2 changes: 1 addition & 1 deletion packages/mask/popups/components/SocialAccounts/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Trans } from '@lingui/macro'
import { Icons } from '@masknet/icons'
import { PopupModalRoutes, type EnhanceableSite, type ProfileAccount } from '@masknet/shared-base'
import { makeStyles } from '@masknet/theme'
Expand All @@ -6,7 +7,6 @@ import { memo } from 'react'
import { AccountAvatar } from '../../pages/Personas/components/AccountAvatar/index.js'
import { useModalNavigate } from '../ActionModal/index.js'
import { ConnectSocialAccounts } from '../ConnectSocialAccounts/index.js'
import { Trans } from '@lingui/macro'

const useStyles = makeStyles()((theme) => ({
tips: {
Expand Down
8 changes: 2 additions & 6 deletions packages/mask/popups/components/TokenPicker/TokenItem.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Trans } from '@lingui/macro'
import { Icons } from '@masknet/icons'
import { NetworkIcon, ProgressiveText, TokenIcon } from '@masknet/shared'
import { NetworkPluginID } from '@masknet/shared-base'
Expand All @@ -18,9 +19,8 @@ import {
useForkRef,
type ListItemProps,
} from '@mui/material'
import { memo, useEffect, useMemo, useRef } from 'react'
import { memo, useMemo, useRef } from 'react'
import { formatTokenBalance } from '../../../shared/index.js'
import { Trans } from '@lingui/macro'

const useStyles = makeStyles()((theme) => {
return {
Expand Down Expand Up @@ -99,10 +99,6 @@ export const TokenItem = memo(function TokenItem({
}, [asset.address, asset.chainId, Utils.explorerResolver.fungibleTokenLink])

const liRef = useRef<HTMLLIElement>(null)
useEffect(() => {
if (!selected) return
liRef.current?.scrollIntoView()
}, [selected])

// #region Try getting balance through RPC.
const providerURL = network?.isCustomized ? network.rpcUrl : undefined
Expand Down
169 changes: 134 additions & 35 deletions packages/mask/popups/components/TokenPicker/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { SelectNetworkSidebar } from '@masknet/shared'
import { t, Trans } from '@lingui/macro'
import { Icons } from '@masknet/icons'
import { EmptyStatus, SelectNetworkSidebar } from '@masknet/shared'
import { EMPTY_LIST, NetworkPluginID } from '@masknet/shared-base'
import { makeStyles } from '@masknet/theme'
import { makeStyles, MaskTextField } from '@masknet/theme'
import type { Web3Helper } from '@masknet/web3-helpers'
import { useFungibleAssets, useNetworks, useWallet } from '@masknet/web3-hooks-base'
import { useAccount, useFungibleAssets, useNetworks, useUserTokenBalances, useWallet } from '@masknet/web3-hooks-base'
import { useOKXTokenList } from '@masknet/web3-hooks-evm'
import { isSameAddress, type ReasonableNetwork } from '@masknet/web3-shared-base'
import { ChainId } from '@masknet/web3-shared-evm'
import {
isEqual,
isGreaterThan,
isSameAddress,
multipliedBy,
rightShift,
type ReasonableNetwork,
} from '@masknet/web3-shared-base'
import { ChainId, getMaskTokenAddress, getNativeTokenAddress } from '@masknet/web3-shared-evm'
import { Box, type BoxProps } from '@mui/material'
import Fuse from 'fuse.js'
import { memo, useCallback, useMemo, useState } from 'react'
import { FixedSizeList, type ListChildComponentProps } from 'react-window'
import { TokenItem, type TokenItemProps } from './TokenItem.js'
Expand Down Expand Up @@ -52,6 +62,13 @@ const useStyles = makeStyles()((theme) => {
sidebar: {
paddingRight: theme.spacing(1),
},
content: {
flexGrow: 1,
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1),
boxSizing: 'border-box',
},
}
})

Expand Down Expand Up @@ -90,16 +107,56 @@ export const TokenPicker = memo(function TokenPicker({
const [standardAssets] = useFungibleAssets(NetworkPluginID.PLUGIN_EVM, undefined, {
chainId,
})
const { data: okxTokens } = useOKXTokenList(chainId, assetSource === AssetSource.Okx)
const isFromOkx = assetSource === AssetSource.Okx
const { data: okxTokens } = useOKXTokenList(chainId, isFromOkx)
const account = useAccount(NetworkPluginID.PLUGIN_EVM)
const { data: balances } = useUserTokenBalances(chainId, account, isFromOkx)
const okxAssets = useMemo(() => {
if (!okxTokens?.length) return EMPTY_LIST
const balanceMap = new Map(standardAssets.map((x) => [x.address.toLowerCase(), x.balance]))
// To reduce queries, get balance from standardAssets and patch okxTokens with it
return okxTokens.map((x) => {
const balance = balanceMap.get(x.address.toLowerCase())
return !balance || balance === '0' ? x : { ...x, balance }
}) as typeof okxTokens
}, [okxTokens, standardAssets])
if (!balances) {
const balanceMap = new Map(standardAssets.map((x) => [x.address.toLowerCase(), x.balance]))
// To reduce queries, get balance from standardAssets and patch okxTokens with it
return okxTokens.map((x) => {
const balance = balanceMap.get(x.address.toLowerCase())
return !balance || balance === '0' ? x : { ...x, balance }
}) as typeof okxTokens
} else {
const assets = okxTokens.map((x) => {
const balance = balances.get(x.address.toLowerCase())
return !balance ? x : { ...x, balance: rightShift(balance.balance, x.decimals).toFixed(0) }
}) as Array<Web3Helper.FungibleAssetScope<void, NetworkPluginID.PLUGIN_EVM>> // typeof okxTokens
return assets.sort((a, z) => {
// native token
const isNativeTokenA = isSameAddress(a.address, getNativeTokenAddress(a.chainId))
if (isNativeTokenA) return -1
const isNativeTokenZ = isSameAddress(z.address, getNativeTokenAddress(z.chainId))
if (isNativeTokenZ) return 1

const aBalance = balances.get(a.address.toLowerCase())
const zBalance = balances.get(z.address.toLowerCase())
const isMaskTokenA = isSameAddress(a.address, getMaskTokenAddress(a.chainId))
const isMaskTokenZ = isSameAddress(z.address, getMaskTokenAddress(z.chainId))
// mask token with position value
const aUSD = multipliedBy(aBalance?.balance ?? 0, aBalance?.tokenPrice ?? 0)
if (aUSD.isPositive() && isMaskTokenA) return -1
const zUSD = multipliedBy(zBalance?.balance ?? 0, zBalance?.tokenPrice ?? 0)
if (zUSD.isPositive() && isMaskTokenZ) return 1

// token value
if (!aUSD.isEqualTo(zUSD)) return zUSD.gt(aUSD) ? 1 : -1

// token balance
if (!isEqual(aBalance?.balance || 0, zBalance?.balance || 0))
return isGreaterThan(zBalance?.balance || 0, aBalance?.balance || 0) ? 1 : -1

// mask token with position value
if (isMaskTokenA) return -1
if (isMaskTokenZ) return 1

return 0
})
}
}, [okxTokens, standardAssets, balances])
const assets = assetSource === AssetSource.Okx ? okxAssets : standardAssets
const handleChainChange = useCallback(
(chainId: Web3Helper.ChainIdAll | undefined) => {
Expand All @@ -108,17 +165,32 @@ export const TokenPicker = memo(function TokenPicker({
},
[onChainChange],
)
const [keyword, setKeyword] = useState('')
const availableAssets = useMemo(() => {
if (!sidebarChainId) return assets
return assets.filter((x) => x.chainId === sidebarChainId)
}, [assets, sidebarChainId])
const fuse = useMemo(() => {
return new Fuse(availableAssets, {
shouldSort: true,
isCaseSensitive: false,
threshold: 0.45,
minMatchCharLength: 1,
keys: ['address', 'symbol', 'name'],
})
}, [availableAssets])
const filteredAssets = useMemo(() => {
if (!keyword) return availableAssets
return fuse.search(keyword).map((x) => x.item)
}, [fuse, keyword])

const isSmartPay = !!useWallet()?.owner
const networks = useNetworks(NetworkPluginID.PLUGIN_EVM, true)
const filteredNetworks = useMemo(() => {
const list = isSmartPay ? networks.filter((x) => x.chainId === ChainId.Polygon && !x.isCustomized) : networks
return chains ? list.filter((x) => chains.includes(x.chainId)) : list
}, [chains, networks, isSmartPay])
const selectedIndex = filteredAssets.findIndex((x) => x.chainId === chainId && isSameAddress(x.address, address))

return (
<Box className={cx(classes.picker, className)} {...rest}>
Expand All @@ -132,28 +204,55 @@ export const TokenPicker = memo(function TokenPicker({
onChainChange={handleChainChange}
/>
: null}
<FixedSizeList
itemCount={availableAssets.length}
itemSize={71}
height={455}
overscanCount={20}
itemData={{
tokens: availableAssets,
networks: filteredNetworks,
chainId,
address,
onSelect,
}}
itemKey={(index, data) => {
const asset = data.tokens[index]
return `${asset.chainId}.${asset.address}`
}}
style={{
scrollbarWidth: 'none',
}}
width="100%">
{Row}
</FixedSizeList>
<div className={classes.content}>
<MaskTextField
value={keyword}
placeholder={t`Name or Contract address e.g. USDC or 0x234...`}
autoFocus
fullWidth
wrapperProps={{
padding: '2px',
}}
InputProps={{
style: { height: 40 },
inputProps: { style: { paddingLeft: 4 } },
startAdornment: <Icons.Search size={18} />,
endAdornment: keyword ? <Icons.Close size={18} onClick={() => setKeyword('')} /> : null,
}}
onChange={(e) => {
setKeyword(e.target.value)
}}
/>
{keyword && !filteredAssets.length ?
<EmptyStatus flexGrow={1} alignItems="center">
<Trans>No matched tokens</Trans>
</EmptyStatus>
: <FixedSizeList
itemCount={filteredAssets.length}
itemSize={71}
height={403}
overscanCount={20}
// show half of previous token
initialScrollOffset={Math.max(0, selectedIndex - 0.5) * 71}
itemData={{
tokens: filteredAssets,
networks: filteredNetworks,
chainId,
address,
onSelect,
}}
itemKey={(index, data) => {
const asset = data.tokens[index]
return `${asset.chainId}.${asset.address}`
}}
style={{
scrollbarWidth: 'none',
}}
width="100%">
{Row}
</FixedSizeList>
}
</div>
</Box>
)
})
10 changes: 6 additions & 4 deletions packages/mask/popups/modals/ChooseToken/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { SingletonModalProps } from '@masknet/shared-base'
import { useSingletonModal } from '@masknet/shared-base-ui'
import type { Web3Helper } from '@masknet/web3-helpers'
import { memo, useState } from 'react'
import { memo, useState, type ReactNode } from 'react'
import { BottomDrawer, TokenPicker, type BottomDrawerProps, type TokenPickerProps } from '../../components/index.js'
import { Trans } from '@lingui/macro'

interface ChooseTokenModalProps extends BottomDrawerProps, Omit<TokenPickerProps, 'title' | 'classes'> {}
interface ChooseTokenModalProps extends Omit<BottomDrawerProps, 'title'>, Omit<TokenPickerProps, 'title' | 'classes'> {
title?: ReactNode
}
const ChooseTokenDrawer = memo(function ChooseTokenDrawer({ title, open, onClose, ...others }: ChooseTokenModalProps) {
return (
<BottomDrawer title={title} open={open} onClose={onClose}>
Expand All @@ -20,7 +22,7 @@ const ChooseTokenDrawer = memo(function ChooseTokenDrawer({ title, open, onClose
)
})

export type ChooseTokenModalOpenProps = Omit<ChooseTokenModalProps, 'title' | 'open'>
export type ChooseTokenModalOpenProps = Omit<ChooseTokenModalProps, 'open'>
export type ChooseTokenModalCloseProps = Web3Helper.FungibleAssetAll | Web3Helper.FungibleTokenAll | void
export function ChooseTokenModal({ ref }: SingletonModalProps<ChooseTokenModalOpenProps, ChooseTokenModalCloseProps>) {
const [props, setProps] = useState<ChooseTokenModalOpenProps>({})
Expand All @@ -33,9 +35,9 @@ export function ChooseTokenModal({ ref }: SingletonModalProps<ChooseTokenModalOp

return (
<ChooseTokenDrawer
title={<Trans>Choose Token</Trans>}
{...props}
open={open}
title={<Trans>Choose Token</Trans>}
onClose={() => dispatch?.close()}
onSelect={(asset) => dispatch?.close(asset)}
/>
Expand Down
4 changes: 2 additions & 2 deletions packages/mask/popups/pages/Friends/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export const PlatformUrlMap: Record<SupportedPlatforms, string> = {
[NextIDPlatform.Unstoppable]: 'https://ud.me/',
[NextIDPlatform.GitHub]: 'https://github.com/',
[NextIDPlatform.SpaceId]: 'https://bscscan.com/address/',
[NextIDPlatform.Farcaster]: 'https://warpcast.com/',
[NextIDPlatform.LENS]: 'https://lenster.xyz/u/',
[NextIDPlatform.Farcaster]: 'https://firefly.mask.social/profile/farcaster/',
[NextIDPlatform.LENS]: 'https://firefly.mask.social/profile/lens/',
[NextIDPlatform.Ethereum]: 'https://etherscan.io/address/',
[NextIDPlatform.Keybase]: 'https://keybase.io/',
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Trans } from '@lingui/macro'
import { useSupportedChains, useTrade, type ShowTooltipOptions } from '@masknet/plugin-trader'
import { usePopupCustomSnackbar } from '@masknet/theme'
import type { Web3Helper } from '@masknet/web3-helpers'
import { TokenType } from '@masknet/web3-shared-base'
import { isNativeTokenAddress, SchemaType, type ChainId } from '@masknet/web3-shared-evm'
import { useCallback, useMemo } from 'react'
import { ChooseTokenModal, ConfirmModal } from '../../modals/modal-controls.js'
import { usePopupCustomSnackbar } from '@masknet/theme'
import { usePopupTheme } from '../../hooks/usePopupTheme.js'
import { AssetSource } from '../../components/index.js'
import { usePopupTheme } from '../../hooks/usePopupTheme.js'
import { ChooseTokenModal, ConfirmModal } from '../../modals/modal-controls.js'

export function useImplementRuntime() {
const chainQuery = useSupportedChains()
Expand All @@ -23,6 +24,7 @@ export function useImplementRuntime() {
const supportedChains = chainQuery.data ?? (await chainQuery.refetch()).data

const picked = await ChooseTokenModal.openAndWaitForClose({
title: <Trans>Select</Trans>,
// Only from token can decide the chain
chainId: ((isSwap ? fromChainId : currentToken?.chainId) || chainId) as ChainId,
address: currentToken?.address,
Expand Down
Loading

0 comments on commit 985e74a

Please sign in to comment.