-
-
- 0}
- openConnectWalletDialog={openPopupsWindow}
- walletName={wallet?.name}
- domain={domain}
- walletAddress={wallet?.address}
- />
-
-
-
- {t('plugin_trader_swap')}
-
-
-
-
-
-
-
+
+
+
+
+ 0}
+ openConnectWalletDialog={openPopupsWindow}
+ walletName={wallet?.name}
+ domain={domain}
+ walletAddress={wallet?.address}
+ />
+
+
+
+ {t('plugin_trader_swap')}
+
+
+
+
+
+
+
+
-
+
)
diff --git a/packages/mask/src/extension/popups/pages/Wallet/TokenDetail/index.tsx b/packages/mask/src/extension/popups/pages/Wallet/TokenDetail/index.tsx
index 42840cfa30f6..ef3bd91b5fa8 100644
--- a/packages/mask/src/extension/popups/pages/Wallet/TokenDetail/index.tsx
+++ b/packages/mask/src/extension/popups/pages/Wallet/TokenDetail/index.tsx
@@ -23,6 +23,7 @@ import Services from '../../../../service'
import { compact, intersectionWith } from 'lodash-unified'
import urlcat from 'urlcat'
import { ActivityList } from '../components/ActivityList'
+import { openWindow } from '@masknet/shared-base-ui'
const useStyles = makeStyles()({
content: {
@@ -93,30 +94,25 @@ const TokenDetail = memo(() => {
open: 'Transak',
code: currentToken?.token.symbol ?? currentToken?.token.name,
})
- window.open(browser.runtime.getURL(url), 'BUY_DIALOG', 'noopener noreferrer')
+ openWindow(browser.runtime.getURL(url), 'BUY_DIALOG')
}
}, [wallet?.address, isActiveSocialNetwork, currentToken])
const openSwapDialog = useCallback(async () => {
- window.open(
- browser.runtime.getURL(
- urlcat(
- 'popups.html#/',
- PopupRoutes.Swap,
- !isSameAddress(nativeToken?.address, currentToken?.token.address)
- ? {
- id: currentToken?.token.address,
- name: currentToken?.token.name,
- symbol: currentToken?.token.symbol,
- contract_address: currentToken?.token.address,
- decimals: currentToken?.token.decimals,
- }
- : {},
- ),
- ),
- 'SWAP_DIALOG',
- 'noopener noreferrer',
+ const url = urlcat(
+ 'popups.html#/',
+ PopupRoutes.Swap,
+ !isSameAddress(nativeToken?.address, currentToken?.token.address)
+ ? {
+ id: currentToken?.token.address,
+ name: currentToken?.token.name,
+ symbol: currentToken?.token.symbol,
+ contract_address: currentToken?.token.address,
+ decimals: currentToken?.token.decimals,
+ }
+ : {},
)
+ openWindow(browser.runtime.getURL(url), 'SWAP_DIALOG')
}, [currentToken, nativeToken])
if (!currentToken) return null
diff --git a/packages/mask/src/manifest.json b/packages/mask/src/manifest.json
index f436056eeecf..e1ca0e475f5f 100644
--- a/packages/mask/src/manifest.json
+++ b/packages/mask/src/manifest.json
@@ -1,6 +1,6 @@
{
"name": "Mask Network",
- "version": "2.6.0",
+ "version": "2.6.1",
"manifest_version": 2,
"permissions": ["storage", "downloads", "webNavigation", "activeTab"],
"optional_permissions": ["
", "notifications", "clipboardRead"],
diff --git a/packages/mask/src/plugin-infra/register.js b/packages/mask/src/plugin-infra/register.js
index 46f4973e183a..38c8b49ce83e 100644
--- a/packages/mask/src/plugin-infra/register.js
+++ b/packages/mask/src/plugin-infra/register.js
@@ -10,6 +10,8 @@ import '@masknet/plugin-rss3'
import '@masknet/plugin-dao'
import '@masknet/plugin-solana'
import '@masknet/plugin-cyberconnect'
+import '@masknet/plugin-go-plus-security'
+import '@masknet/plugin-cross-chain-bridge'
import '../plugins/Wallet'
import '../plugins/EVM'
import '../plugins/RedPacket'
diff --git a/packages/mask/src/plugins/ArtBlocks/SNSAdaptor/PurchaseDialog.tsx b/packages/mask/src/plugins/ArtBlocks/SNSAdaptor/PurchaseDialog.tsx
index d53bbbbd27ac..b01ad22f3610 100644
--- a/packages/mask/src/plugins/ArtBlocks/SNSAdaptor/PurchaseDialog.tsx
+++ b/packages/mask/src/plugins/ArtBlocks/SNSAdaptor/PurchaseDialog.tsx
@@ -12,7 +12,6 @@ import {
Typography,
} from '@mui/material'
import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary'
import {
ERC20TokenDetailed,
@@ -23,11 +22,11 @@ import {
TransactionStateType,
} from '@masknet/web3-shared-evm'
import { Trans } from 'react-i18next'
-import { usePurchaseCallback } from '../hooks/usePurchaseCallback'
-import { TokenAmountPanel } from '@masknet/shared'
-import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary'
-import { leftShift } from '@masknet/web3-shared-base'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog, TokenAmountPanel } from '@masknet/shared'
+import { leftShift } from '@masknet/web3-shared-base'
+import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary'
+import { usePurchaseCallback } from '../hooks/usePurchaseCallback'
import { WalletMessages } from '../../Wallet/messages'
import type { Project } from '../types'
diff --git a/packages/mask/src/plugins/Avatar/SNSAdaptor/AddNFT.tsx b/packages/mask/src/plugins/Avatar/SNSAdaptor/AddNFT.tsx
index a01ed117f905..97c7c707fe60 100644
--- a/packages/mask/src/plugins/Avatar/SNSAdaptor/AddNFT.tsx
+++ b/packages/mask/src/plugins/Avatar/SNSAdaptor/AddNFT.tsx
@@ -2,7 +2,7 @@ import { makeStyles } from '@masknet/theme'
import { ERC721TokenDetailed, isSameAddress, useAccount } from '@masknet/web3-shared-evm'
import { Button, DialogContent, Typography } from '@mui/material'
import { useCallback, useState } from 'react'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import { InputBox } from '../../../extension/options-page/DashboardComponents/InputBox'
import { useI18N } from '../../../utils'
import { createNFT } from '../utils'
diff --git a/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTBadge.tsx b/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTBadge.tsx
index be85dd024b92..42a0d94f1b07 100644
--- a/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTBadge.tsx
+++ b/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTBadge.tsx
@@ -1,3 +1,4 @@
+import { openWindow } from '@masknet/shared-base-ui'
import { makeStyles, useStylesExtends } from '@masknet/theme'
import { resolveOpenSeaLink } from '@masknet/web3-shared-evm'
import Link from '@mui/material/Link'
@@ -42,7 +43,7 @@ export function NFTBadge(props: NFTBadgeProps) {
className={classes.root}
onClick={(e) => {
e.preventDefault()
- window.open(resolveOpenSeaLink(avatar.address, avatar.tokenId), '_blank')
+ openWindow(resolveOpenSeaLink(avatar.address, avatar.tokenId))
}}>
{
padding: theme.spacing(8, 0),
},
markdown: {
- 'text-overflow': 'ellipsis',
+ textOverflow: 'ellipsis',
display: '-webkit-box',
- '-webkit-box-orient': 'vertical',
- '-webkit-line-clamp': '3',
+ webkitBoxOrient: 'vertical',
+ webkitLineClamp: '3',
},
}
})
diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx
index fd1fef98b836..cd7c207be1eb 100644
--- a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx
+++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx
@@ -16,8 +16,8 @@ import BigNumber from 'bignumber.js'
import { FungibleTokenDetailed, EthereumTokenType, useAccount, useFungibleTokenWatched } from '@masknet/web3-shared-evm'
import formatDateTime from 'date-fns/format'
import { useI18N } from '../../../utils'
+import { InjectedDialog } from '@masknet/shared'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
import { UnreviewedWarning } from './UnreviewedWarning'
import ActionButton, { ActionButtonPromise } from '../../../extension/options-page/DashboardComponents/ActionButton'
import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary'
diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/PostListingDialog.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/PostListingDialog.tsx
index 29ba310a63ed..524573ebd2ad 100644
--- a/packages/mask/src/plugins/Collectible/SNSAdaptor/PostListingDialog.tsx
+++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/PostListingDialog.tsx
@@ -2,7 +2,7 @@ import { useState } from 'react'
import { DialogContent, Tab, Tabs } from '@mui/material'
import { makeStyles } from '@masknet/theme'
import { useI18N } from '../../../utils'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import { ListingByPriceCard } from './ListingByPriceCard'
import { ListingByHighestBidCard } from './ListingByHighestBidCard'
import { useFungibleTokenWatched } from '@masknet/web3-shared-evm'
diff --git a/packages/mask/src/plugins/Collectible/hooks/useAssetOrder.ts b/packages/mask/src/plugins/Collectible/hooks/useAssetOrder.ts
index ae211e1ea6cd..068223e11a47 100644
--- a/packages/mask/src/plugins/Collectible/hooks/useAssetOrder.ts
+++ b/packages/mask/src/plugins/Collectible/hooks/useAssetOrder.ts
@@ -10,7 +10,7 @@ import type { AssetOrder, CollectibleToken } from '../types'
export function useAssetOrder(provider: NonFungibleAssetProvider, token?: CollectibleToken) {
return useAsyncRetry(async () => {
- if (!token) return
+ if (!token?.contractAddress || !token?.tokenId) return
switch (provider) {
case NonFungibleAssetProvider.OPENSEA:
const openSeaResponse = await PluginCollectibleRPC.getAssetFromSDK(token.contractAddress, token.tokenId)
@@ -37,5 +37,5 @@ export function useAssetOrder(provider: NonFungibleAssetProvider, token?: Collec
default:
unreachable(provider)
}
- }, [provider, token])
+ }, [provider, token?.contractAddress, token?.tokenId])
}
diff --git a/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/CheckoutDialog.tsx b/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/CheckoutDialog.tsx
index 33cebe8fafbf..bd4e1683115d 100644
--- a/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/CheckoutDialog.tsx
+++ b/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/CheckoutDialog.tsx
@@ -4,11 +4,11 @@ import { makeStyles } from '@masknet/theme'
import { first } from 'lodash-unified'
import BigNumber from 'bignumber.js'
import { useChainId, useFungibleTokenWatched, TransactionStateType, formatBalance } from '@masknet/web3-shared-evm'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog } from '@masknet/shared'
import { useI18N } from '../../../utils'
import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton'
import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary'
-import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
import { WalletMessages } from '../../Wallet/messages'
import type { useAsset } from '../hooks/useAsset'
import { resolvePaymentTokensOnCryptoartAI, resolveAssetLinkOnCryptoartAI } from '../pipes'
diff --git a/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/MakeOfferDialog.tsx b/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/MakeOfferDialog.tsx
index ebe26afb17a2..eb1a07ac67be 100644
--- a/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/MakeOfferDialog.tsx
+++ b/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/MakeOfferDialog.tsx
@@ -11,12 +11,12 @@ import {
formatBalance,
TransactionStateType,
} from '@masknet/web3-shared-evm'
+import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog } from '@masknet/shared'
import { useI18N } from '../../../utils'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton'
import { SelectTokenAmountPanel } from '../../ITO/SNSAdaptor/SelectTokenAmountPanel'
import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary'
-import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
import { WalletMessages } from '../../Wallet/messages'
import type { useAsset } from '../hooks/useAsset'
import { resolvePaymentTokensOnCryptoartAI, resolveAssetLinkOnCryptoartAI } from '../pipes'
diff --git a/packages/mask/src/plugins/EVM/UI/Web3State/createNonFungibleToken.ts b/packages/mask/src/plugins/EVM/UI/Web3State/createNonFungibleToken.ts
new file mode 100644
index 000000000000..218ba80a7734
--- /dev/null
+++ b/packages/mask/src/plugins/EVM/UI/Web3State/createNonFungibleToken.ts
@@ -0,0 +1,27 @@
+import { TokenType, Web3Plugin } from '@masknet/plugin-infra'
+import type { ERC721TokenDetailed } from '@masknet/web3-shared-evm'
+
+export function createNonFungibleToken(token: ERC721TokenDetailed) {
+ return {
+ ...token,
+ id: `${token.contractDetailed.address}_${token.tokenId}`,
+ tokenId: token.tokenId,
+ chainId: token.contractDetailed.chainId,
+ type: TokenType.NonFungible,
+ name: token.info.name ?? `${token.contractDetailed.name} ${token.tokenId}`,
+ description: token.info.description ?? '',
+ owner: token.info.owner,
+ contract: {
+ ...token.contractDetailed,
+ type: TokenType.NonFungible,
+ id: token.contractDetailed.address,
+ },
+ metadata: {
+ name: token.info.name ?? `${token.contractDetailed.name} ${token.tokenId}`,
+ description: token.info.description ?? '',
+ mediaType: 'Unknown',
+ iconURL: token.contractDetailed.iconURL,
+ assetURL: token.info.mediaUrl,
+ },
+ } as Web3Plugin.NonFungibleToken
+}
diff --git a/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts b/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts
index 69f40729cae1..d52b42264d73 100644
--- a/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts
+++ b/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts
@@ -1,33 +1,40 @@
-import Web3 from 'web3'
import {
+ getRegisteredWeb3Networks,
+ NetworkPluginID,
+ Pageable,
+ Pagination,
+ TokenType,
+ Web3Plugin,
+} from '@masknet/plugin-infra'
+import BalanceCheckerABI from '@masknet/web3-contracts/abis/BalanceChecker.json'
+import ERC721ABI from '@masknet/web3-contracts/abis/ERC721.json'
+import type { ERC721 } from '@masknet/web3-contracts/types/ERC721'
+import { TokenPrice } from '@masknet/web3-providers'
+import { pow10 } from '@masknet/web3-shared-base'
+import {
+ ChainId,
createContract,
+ createExternalProvider,
createNativeToken,
+ CurrencyType,
ERC721TokenDetailed,
formatEthereumAddress,
- getERC721TokenDetailedFromChain,
+ FungibleAssetProvider,
+ getCoinGeckoCoinId,
getERC721TokenAssetFromChain,
+ getERC721TokenDetailedFromChain,
getEthereumConstants,
isSameAddress,
- Web3ProviderType,
- FungibleAssetProvider,
- createExternalProvider,
- getCoinGeckoCoinId,
- CurrencyType,
PriceRecord,
- ChainId,
+ Web3ProviderType,
} from '@masknet/web3-shared-evm'
import BigNumber from 'bignumber.js'
-import { Pageable, Pagination, TokenType, Web3Plugin } from '@masknet/plugin-infra'
-import BalanceCheckerABI from '@masknet/web3-contracts/abis/BalanceChecker.json'
-import ERC721ABI from '@masknet/web3-contracts/abis/ERC721.json'
-import type { AbiItem } from 'web3-utils'
import { uniqBy } from 'lodash-unified'
-import { PLUGIN_NETWORKS } from '../../constants'
+import Web3 from 'web3'
+import type { AbiItem } from 'web3-utils'
import { makeSortAssertWithoutChainFn } from '../../utils/token'
import { createGetLatestBalance } from './createGetLatestBalance'
-import type { ERC721 } from '@masknet/web3-contracts/types/ERC721'
-import { pow10 } from '@masknet/web3-shared-base'
-import { TokenPrice } from '@masknet/web3-providers'
+import { createNonFungibleToken } from './createNonFungibleToken'
// tokens unavailable neither from api or balance checker.
// https://forum.conflux.fun/t/how-to-upvote-debank-proposal-for-conflux-espace-integration/13935
@@ -38,7 +45,10 @@ export const getFungibleAssetsFn =
async (address: string, providerType: string, network: Web3Plugin.NetworkDescriptor, pagination?: Pagination) => {
const chainId = context.chainId.getCurrentValue()
const wallet = context.wallets.getCurrentValue().find((x) => isSameAddress(x.address, address))
- const networks = PLUGIN_NETWORKS
+ const networks = getRegisteredWeb3Networks().filter(
+ (x) => NetworkPluginID.PLUGIN_EVM === x.networkSupporterPluginID && x.isMainnet,
+ )
+ const supportedNetworkIds = networks.map((x) => x.chainId)
const trustedTokens = uniqBy(
context.erc20Tokens
.getCurrentValue()
@@ -51,22 +61,24 @@ export const getFungibleAssetsFn =
)
const { BALANCE_CHECKER_ADDRESS } = getEthereumConstants(chainId)
const dataFromProvider = await context.getAssetsList(address, FungibleAssetProvider.DEBANK)
- const assetsFromProvider: Web3Plugin.Asset[] = dataFromProvider.map((x) => ({
- id: x.token.address,
- chainId: x.token.chainId,
- balance: x.balance,
- price: x.price,
- value: x.value,
- logoURI: x.logoURI,
- token: {
- ...x.token,
- type: TokenType.Fungible,
- name: x.token.name ?? 'Unknown Token',
- symbol: x.token.symbol ?? 'Unknown',
+ const assetsFromProvider = dataFromProvider
+ .map>((x) => ({
id: x.token.address,
chainId: x.token.chainId,
- },
- }))
+ balance: x.balance,
+ price: x.price,
+ value: x.value,
+ logoURI: x.logoURI,
+ token: {
+ ...x.token,
+ name: x.token.name ?? 'Unknown Token',
+ symbol: x.token.symbol ?? 'Unknown',
+ id: x.token.address,
+ chainId: x.token.chainId,
+ type: TokenType.Fungible,
+ },
+ }))
+ .filter((x) => supportedNetworkIds.includes(x.chainId))
const balanceCheckerContract = createContract(
web3,
@@ -215,31 +227,7 @@ export const getNonFungibleTokenFn =
const tokenFromProvider = socket.getResult(socketId)
const allData: Web3Plugin.NonFungibleToken[] = [...tokenInDb, ...tokenFromProvider]
- .map(
- (x) =>
- ({
- ...x,
- id: `${x.contractDetailed.address}_${x.tokenId}`,
- tokenId: x.tokenId,
- chainId: x.contractDetailed.chainId,
- type: TokenType.NonFungible,
- name: x.info.name ?? `${x.contractDetailed.name} ${x.tokenId}`,
- description: x.info.description ?? '',
- owner: x.info.owner,
- contract: {
- ...x.contractDetailed,
- type: TokenType.NonFungible,
- id: x.contractDetailed.address,
- },
- metadata: {
- name: x.info.name ?? `${x.contractDetailed.name} ${x.tokenId}`,
- description: x.info.description ?? '',
- mediaType: 'Unknown',
- iconURL: x.contractDetailed.iconURL,
- assetURL: x.info.mediaUrl,
- },
- } as Web3Plugin.NonFungibleToken),
- )
+ .map(createNonFungibleToken)
.filter((x) => isSameAddress(x.owner, address))
.filter((x) => !network || x.chainId === network.chainId)
diff --git a/packages/mask/src/plugins/EVM/UI/components/ProviderIconClickBait.tsx b/packages/mask/src/plugins/EVM/UI/components/ProviderIconClickBait.tsx
index 6c9618e7c638..f0564cb61c90 100644
--- a/packages/mask/src/plugins/EVM/UI/components/ProviderIconClickBait.tsx
+++ b/packages/mask/src/plugins/EVM/UI/components/ProviderIconClickBait.tsx
@@ -1,7 +1,7 @@
import { useCallback, cloneElement, isValidElement } from 'react'
import { unreachable } from '@dimensiondev/kit'
import type { Web3Plugin } from '@masknet/plugin-infra'
-import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { openWindow, useRemoteControlledDialog } from '@masknet/shared-base-ui'
import { isDashboardPage } from '@masknet/shared-base'
import {
getChainIdFromNetworkType,
@@ -47,7 +47,7 @@ export function ProviderIconClickBait({
if (!isProviderAvailable) {
const downloadLink = resolveProviderDownloadLink(providerType)
- if (downloadLink) window.open(downloadLink, '_blank', 'noopener noreferrer')
+ openWindow(downloadLink)
return
}
}
diff --git a/packages/mask/src/plugins/EVM/constants.ts b/packages/mask/src/plugins/EVM/constants.ts
index 2c17f3d96cbf..2a508a0600db 100644
--- a/packages/mask/src/plugins/EVM/constants.ts
+++ b/packages/mask/src/plugins/EVM/constants.ts
@@ -1,8 +1,8 @@
-import { PluginId, Web3Plugin } from '@masknet/plugin-infra'
+import { NetworkPluginID, Web3Plugin } from '@masknet/plugin-infra'
import { ChainId, NetworkType, ProviderType } from '@masknet/web3-shared-evm'
-export const PLUGIN_ID = PluginId.EVM
-export const PLUGIN_META_KEY = `${PluginId.EVM}:1`
+export const PLUGIN_ID = NetworkPluginID.PLUGIN_EVM
+export const PLUGIN_META_KEY = `${PLUGIN_ID}:1`
export const PLUGIN_NAME = 'EVM'
export const PLUGIN_ICON = '\u039E'
export const PLUGIN_DESCRIPTION = ''
diff --git a/packages/mask/src/plugins/EVM/messages.ts b/packages/mask/src/plugins/EVM/messages.ts
index 8a8ea304aae9..28bf194d5b6d 100644
--- a/packages/mask/src/plugins/EVM/messages.ts
+++ b/packages/mask/src/plugins/EVM/messages.ts
@@ -25,8 +25,10 @@ export interface EVM_Messages {
rpc: unknown
}
+const evmEventEmitter: PluginMessageEmitter = createPluginMessage(PLUGIN_ID, serializer)
+
export const EVM_Messages: { events: PluginMessageEmitter } = {
- events: createPluginMessage(PLUGIN_ID, serializer),
+ events: evmEventEmitter,
}
export const EVM_RPC = createPluginRPC(PLUGIN_ID, () => import('./services'), EVM_Messages.events.rpc)
diff --git a/packages/mask/src/plugins/External/components/CompositionEntry.tsx b/packages/mask/src/plugins/External/components/CompositionEntry.tsx
index f27b2b708379..d24e9f18910e 100644
--- a/packages/mask/src/plugins/External/components/CompositionEntry.tsx
+++ b/packages/mask/src/plugins/External/components/CompositionEntry.tsx
@@ -4,7 +4,7 @@ import { DialogContent } from '@mui/material'
import { useEffect } from 'react'
import { MaskMessages, useI18N } from '../../../utils'
import { PluginLoader } from './PluginLoader'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
export function ThirdPartyPluginCompositionEntry(props: Plugin.SNSAdaptor.CompositionDialogEntry_DialogProps) {
const { t } = useI18N()
diff --git a/packages/mask/src/plugins/FindTruman/SNSAdaptor/FindTrumanDialog.tsx b/packages/mask/src/plugins/FindTruman/SNSAdaptor/FindTrumanDialog.tsx
index 990b9695b833..6450ab921425 100644
--- a/packages/mask/src/plugins/FindTruman/SNSAdaptor/FindTrumanDialog.tsx
+++ b/packages/mask/src/plugins/FindTruman/SNSAdaptor/FindTrumanDialog.tsx
@@ -1,6 +1,6 @@
import { Box, DialogContent } from '@mui/material'
import { TabContext, TabPanel } from '@mui/lab'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import { makeStyles, useStylesExtends, useTabs } from '@masknet/theme'
import { WalletStatusBox } from '../../../components/shared/WalletStatusBox'
import AbstractTab, { AbstractTabProps } from '../../../components/shared/AbstractTab'
diff --git a/packages/mask/src/plugins/FindTruman/SNSAdaptor/MainDialog.tsx b/packages/mask/src/plugins/FindTruman/SNSAdaptor/MainDialog.tsx
index d9af958dbc43..b2278b82343a 100644
--- a/packages/mask/src/plugins/FindTruman/SNSAdaptor/MainDialog.tsx
+++ b/packages/mask/src/plugins/FindTruman/SNSAdaptor/MainDialog.tsx
@@ -2,7 +2,7 @@ import { DialogContent, Card, Grid, Alert, Box, Typography, Button } from '@mui/
import { makeStyles } from '@masknet/theme'
import { useContext, useEffect, useState } from 'react'
import { useI18N } from '../../../utils'
-import { InjectedDialog, InjectedDialogProps } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog, InjectedDialogProps } from '@masknet/shared'
import { useAccount } from '@masknet/web3-shared-evm'
import { fetchConst, fetchUserParticipatedStoryStatus } from '../Worker/apis'
import type { UserStoryStatus, FindTrumanConst } from '../types'
diff --git a/packages/mask/src/plugins/FindTruman/SNSAdaptor/ParticipatePanel.tsx b/packages/mask/src/plugins/FindTruman/SNSAdaptor/ParticipatePanel.tsx
index b1453bdedc6b..2cabace3e88c 100644
--- a/packages/mask/src/plugins/FindTruman/SNSAdaptor/ParticipatePanel.tsx
+++ b/packages/mask/src/plugins/FindTruman/SNSAdaptor/ParticipatePanel.tsx
@@ -6,7 +6,7 @@ import { Box, Button, Card, DialogActions, DialogContent, Typography } from '@mu
import { TabContext, TabPanel } from '@mui/lab'
import StageCard from './StageCard'
import { useControlledDialog } from '../../../utils'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
import OptionsCard from './OptionsCard'
import ResultCard from './ResultCard'
diff --git a/packages/mask/src/plugins/FindTruman/SNSAdaptor/PartsPanel.tsx b/packages/mask/src/plugins/FindTruman/SNSAdaptor/PartsPanel.tsx
index a6bcee3cb432..00ca5c8ba097 100644
--- a/packages/mask/src/plugins/FindTruman/SNSAdaptor/PartsPanel.tsx
+++ b/packages/mask/src/plugins/FindTruman/SNSAdaptor/PartsPanel.tsx
@@ -20,7 +20,7 @@ import {
Typography,
} from '@mui/material'
import formatDateTime from 'date-fns/format'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import { useContext, useMemo, useState } from 'react'
import { LoadingButton } from '@mui/lab'
import getUnixTime from 'date-fns/getUnixTime'
diff --git a/packages/mask/src/plugins/FindTruman/SNSAdaptor/index.tsx b/packages/mask/src/plugins/FindTruman/SNSAdaptor/index.tsx
index 93b229a4f97e..5c9c787ce528 100644
--- a/packages/mask/src/plugins/FindTruman/SNSAdaptor/index.tsx
+++ b/packages/mask/src/plugins/FindTruman/SNSAdaptor/index.tsx
@@ -1,11 +1,13 @@
import { base } from '../base'
-import { useMemo, Suspense } from 'react'
+import { useMemo, Suspense, useState } from 'react'
import { Skeleton } from '@mui/material'
import { makeStyles } from '@masknet/theme'
import { type Plugin, usePostInfoDetails, usePluginWrapper } from '@masknet/plugin-infra'
import { extractTextFromTypedMessage } from '@masknet/typed-message'
import { parseURL } from '@masknet/shared-base'
import { PostInspector } from './PostInspector'
+import { ApplicationEntry } from '@masknet/shared'
+import { FindTrumanDialog } from './FindTrumanDialog'
const useStyles = makeStyles()((theme) => {
return {
@@ -84,6 +86,25 @@ const sns: Plugin.SNSAdaptor.Definition = {
if (!link) return null
return
},
+ ApplicationEntries: [
+ {
+ RenderEntryComponent({ disabled }) {
+ const [open, setOpen] = useState(false)
+ return (
+ <>
+ setOpen(true)}
+ />
+ setOpen(false)} />
+ >
+ )
+ },
+ defaultSortingPriority: 11,
+ },
+ ],
}
export default sns
diff --git a/packages/mask/src/components/shared/assets/findtruman.png b/packages/mask/src/plugins/FindTruman/assets/findtruman.png
similarity index 100%
rename from packages/mask/src/components/shared/assets/findtruman.png
rename to packages/mask/src/plugins/FindTruman/assets/findtruman.png
diff --git a/packages/mask/src/plugins/FindTruman/base.ts b/packages/mask/src/plugins/FindTruman/base.ts
index 8b9bb909e652..8ded5d9bb91f 100644
--- a/packages/mask/src/plugins/FindTruman/base.ts
+++ b/packages/mask/src/plugins/FindTruman/base.ts
@@ -1,4 +1,5 @@
-import type { Plugin } from '@masknet/plugin-infra'
+import { Plugin, NetworkPluginID } from '@masknet/plugin-infra'
+import { ChainId } from '@masknet/web3-shared-evm'
import { FIND_TRUMAN_PLUGIN_ID, FIND_TRUMAN_PLUGIN_NAME } from './constants'
export const base: Plugin.Shared.Definition = {
@@ -13,6 +14,23 @@ export const base: Plugin.Shared.Definition = {
architecture: { app: true, web: true },
networks: { type: 'opt-out', networks: {} },
target: 'stable',
+ web3: {
+ [NetworkPluginID.PLUGIN_EVM]: {
+ supportedChainIds: [
+ ChainId.Mainnet,
+ ChainId.BSC,
+ ChainId.Matic,
+ ChainId.Arbitrum,
+ ChainId.xDai,
+ ChainId.Fantom,
+ ChainId.Avalanche,
+ ChainId.Aurora,
+ ChainId.Conflux,
+ ],
+ },
+ [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] },
+ [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] },
+ },
},
contribution: {
postContent: new Set([
diff --git a/packages/mask/src/plugins/Gitcoin/SNSAdaptor/DonateDialog.tsx b/packages/mask/src/plugins/Gitcoin/SNSAdaptor/DonateDialog.tsx
index edf2dcfd6c97..fa58d7bee67e 100644
--- a/packages/mask/src/plugins/Gitcoin/SNSAdaptor/DonateDialog.tsx
+++ b/packages/mask/src/plugins/Gitcoin/SNSAdaptor/DonateDialog.tsx
@@ -1,3 +1,7 @@
+import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { usePickToken, InjectedDialog } from '@masknet/shared'
+import { makeStyles, useStylesExtends } from '@masknet/theme'
+import { rightShift } from '@masknet/web3-shared-base'
import {
EthereumTokenType,
formatBalance,
@@ -5,29 +9,24 @@ import {
TransactionStateType,
useAccount,
useChainId,
+ useFungibleTokenBalance,
useGitcoinConstants,
useNativeTokenDetailed,
- useFungibleTokenBalance,
} from '@masknet/web3-shared-evm'
import { DialogContent, Link, Typography } from '@mui/material'
-import { makeStyles, useStylesExtends } from '@masknet/theme'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { Trans } from 'react-i18next'
-import { v4 as uuid } from 'uuid'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton'
import { activatedSocialNetworkUI } from '../../../social-network'
-import { isTwitter } from '../../../social-network-adaptor/twitter.com/base'
import { isFacebook } from '../../../social-network-adaptor/facebook.com/base'
+import { isTwitter } from '../../../social-network-adaptor/twitter.com/base'
import { useI18N } from '../../../utils'
-import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary'
import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary'
import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel'
-import { SelectTokenDialogEvent, WalletMessages } from '../../Wallet/messages'
+import { WalletMessages } from '../../Wallet/messages'
import { useDonateCallback } from '../hooks/useDonateCallback'
import { PluginGitcoinMessages } from '../messages'
-import { rightShift } from '@masknet/web3-shared-base'
const useStyles = makeStyles()((theme) => ({
paper: {
@@ -87,27 +86,14 @@ export function DonateDialog(props: DonateDialogProps) {
// #endregion
// #region select token dialog
- const [id] = useState(uuid())
- const { setDialog: setSelectTokenDialog } = useRemoteControlledDialog(
- WalletMessages.events.selectTokenDialogUpdated,
- useCallback(
- (ev: SelectTokenDialogEvent) => {
- if (ev.open || !ev.token || ev.uuid !== id) return
- setToken(ev.token)
- },
- [id],
- ),
- )
- const onSelectTokenChipClick = useCallback(() => {
- setSelectTokenDialog({
- open: true,
- uuid: id,
+ const pickToken = usePickToken()
+ const onSelectTokenChipClick = useCallback(async () => {
+ const pickedToken = await pickToken({
disableNativeToken: false,
- FungibleTokenListProps: {
- selectedTokens: token ? [token.address] : [],
- },
+ selectedTokens: token?.address ? [token.address] : [],
})
- }, [id, token?.address])
+ if (pickedToken) setToken(pickedToken)
+ }, [pickToken, token?.address])
// #endregion
// #region amount
diff --git a/packages/mask/src/plugins/Gitcoin/base.ts b/packages/mask/src/plugins/Gitcoin/base.ts
index 351f388d12bd..e3387df5a855 100644
--- a/packages/mask/src/plugins/Gitcoin/base.ts
+++ b/packages/mask/src/plugins/Gitcoin/base.ts
@@ -16,6 +16,8 @@ export const base: Plugin.Shared.Definition = {
[NetworkPluginID.PLUGIN_EVM]: {
supportedChainIds: [ChainId.Mainnet, ChainId.Rinkeby, ChainId.Matic, ChainId.Mumbai],
},
+ [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] },
+ [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] },
},
},
contribution: { postContent: new Set([/https:\/\/gitcoin.co\/grants\/\d+/]) },
diff --git a/packages/mask/src/plugins/GoodGhosting/UI/GameActionDialog.tsx b/packages/mask/src/plugins/GoodGhosting/UI/GameActionDialog.tsx
index 51ef10098ffb..5e02a0039cb7 100644
--- a/packages/mask/src/plugins/GoodGhosting/UI/GameActionDialog.tsx
+++ b/packages/mask/src/plugins/GoodGhosting/UI/GameActionDialog.tsx
@@ -1,6 +1,6 @@
import { Box, Button, DialogContent, DialogActions, Typography } from '@mui/material'
import { makeStyles } from '@masknet/theme'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary'
import { ERC20TokenDetailed, formatBalance, useERC20TokenBalance } from '@masknet/web3-shared-evm'
import type { GoodGhostingInfo } from '../types'
diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/ClaimAllDialog.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/ClaimAllDialog.tsx
index 1b786419ad6c..d58a66ae9710 100644
--- a/packages/mask/src/plugins/ITO/SNSAdaptor/ClaimAllDialog.tsx
+++ b/packages/mask/src/plugins/ITO/SNSAdaptor/ClaimAllDialog.tsx
@@ -1,10 +1,10 @@
-import { usePluginIDContext, PluginId, useActivatedPlugin } from '@masknet/plugin-infra'
+import { useCurrentWeb3NetworkPluginID, PluginId, useActivatedPlugin } from '@masknet/plugin-infra'
import { useCallback, useEffect, useState, useLayoutEffect, useRef } from 'react'
import { flatten, uniq } from 'lodash-unified'
import formatDateTime from 'date-fns/format'
import { SnackbarProvider, makeStyles } from '@masknet/theme'
-import { FormattedBalance } from '@masknet/shared'
-import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { openWindow, useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog, FormattedBalance } from '@masknet/shared'
import { DialogContent, CircularProgress, Typography, List, ListItem, useTheme } from '@mui/material'
import {
formatBalance,
@@ -26,7 +26,6 @@ import { useI18N } from '../../../utils'
import { Flags } from '../../../../shared'
import { useSpaceStationCampaignInfo } from './hooks/useSpaceStationCampaignInfo'
import { NftAirdropCard } from './NftAirdropCard'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
import { useClaimAll } from './hooks/useClaimAll'
import { WalletMessages } from '../../Wallet/messages'
import { useClaimCallback } from './hooks/useClaimCallback'
@@ -230,7 +229,7 @@ export function ClaimAllDialog(props: ClaimAllDialogProps) {
const { t } = useI18N()
const { open, onClose } = props
const ITO_Definition = useActivatedPlugin(PluginId.ITO, 'any')
- const pluginId = usePluginIDContext()
+ const pluginId = useCurrentWeb3NetworkPluginID()
const chainIdList = ITO_Definition?.enableRequirement.web3?.[pluginId]?.supportedChainIds ?? []
const DialogRef = useRef(null)
const account = useAccount()
@@ -301,7 +300,7 @@ export function ClaimAllDialog(props: ClaimAllDialogProps) {
if (claimState.type === TransactionStateType.HASH) {
const { hash } = claimState
setTimeout(() => {
- window.open(resolveTransactionLinkOnExplorer(chainId, hash), '_blank', 'noopener noreferrer')
+ openWindow(resolveTransactionLinkOnExplorer(chainId, hash))
}, 2000)
return
}
diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/CompositionDialog.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/CompositionDialog.tsx
index 0057a4bb343a..2a09f23d9c3a 100644
--- a/packages/mask/src/plugins/ITO/SNSAdaptor/CompositionDialog.tsx
+++ b/packages/mask/src/plugins/ITO/SNSAdaptor/CompositionDialog.tsx
@@ -5,7 +5,7 @@ import { makeStyles } from '@masknet/theme'
import { useI18N } from '../../../utils'
import { useCurrentIdentity } from '../../../components/DataSource/useActivatedUI'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
-import { InjectedDialog, InjectedDialogProps } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog, InjectedDialogProps, MINDS_ID } from '@masknet/shared'
import { ITO_MetaKey_2, MSG_DELIMITER } from '../constants'
import { DialogTabs, JSON_PayloadInMask } from '../types'
import { CreateForm } from './CreateForm'
@@ -20,7 +20,6 @@ import { ConfirmDialog } from './ConfirmDialog'
import { WalletMessages } from '../../Wallet/messages'
import { omit, set } from 'lodash-unified'
import { useCompositionContext } from '@masknet/plugin-infra'
-import { MINDS_ID } from '../../../social-network-adaptor/minds.com/base'
import { activatedSocialNetworkUI } from '../../../social-network'
interface StyleProps {
diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/ExchangeTokenPanel.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/ExchangeTokenPanel.tsx
index 096a903f9ff5..976730baf092 100644
--- a/packages/mask/src/plugins/ITO/SNSAdaptor/ExchangeTokenPanel.tsx
+++ b/packages/mask/src/plugins/ITO/SNSAdaptor/ExchangeTokenPanel.tsx
@@ -1,15 +1,14 @@
-import { EthereumTokenType, FungibleTokenDetailed, useFungibleTokenBalance } from '@masknet/web3-shared-evm'
-import { IconButton, Paper } from '@mui/material'
import { makeStyles } from '@masknet/theme'
+import { EthereumTokenType, FungibleTokenDetailed, useFungibleTokenBalance } from '@masknet/web3-shared-evm'
import AddIcon from '@mui/icons-material/AddOutlined'
import RemoveIcon from '@mui/icons-material/RemoveOutlined'
+import { IconButton, Paper } from '@mui/material'
import { useCallback, useEffect, useState } from 'react'
-import { v4 as uuid } from 'uuid'
+import { usePickToken } from '@masknet/shared'
import { useI18N } from '../../../utils'
-import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
import type { TokenAmountPanelProps } from '../../../web3/UI/TokenAmountPanel'
import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel'
-import { SelectTokenDialogEvent, WalletMessages } from '../../Wallet/messages'
+
const useStyles = makeStyles()((theme) => ({
root: {
width: '100%',
@@ -80,28 +79,21 @@ export function ExchangeTokenPanel(props: ExchangeTokenPanelProps) {
const { t } = useI18N()
const { classes } = useStyles()
// #region select token dialog
- const [id] = useState(uuid())
- const { setDialog: setSelectTokenDialog } = useRemoteControlledDialog(
- WalletMessages.events.selectTokenDialogUpdated,
- useCallback(
- (ev: SelectTokenDialogEvent) => {
- if (ev.open || !ev.token || ev.uuid !== id) return
- onExchangeTokenChange(ev.token, dataIndex)
- },
- [id, dataIndex],
- ),
- )
- const onSelectTokenChipClick = useCallback(() => {
- setSelectTokenDialog({
- open: true,
- uuid: id,
+ const pickToken = usePickToken()
+ const onSelectTokenChipClick = useCallback(async () => {
+ const picked = await pickToken({
disableNativeToken: isSell,
- FungibleTokenListProps: {
- blacklist: excludeTokensAddress,
- selectedTokens: [exchangeToken?.address ?? '', ...selectedTokensAddress],
- },
+ blacklist: excludeTokensAddress,
+ selectedTokens: [exchangeToken?.address || '', ...selectedTokensAddress],
})
- }, [id, isSell, exchangeToken, excludeTokensAddress.sort().join(), selectedTokensAddress.sort().join()])
+ if (picked) onExchangeTokenChange(picked, dataIndex)
+ }, [
+ isSell,
+ dataIndex,
+ exchangeToken?.address,
+ excludeTokensAddress.sort().join(),
+ selectedTokensAddress.sort().join(),
+ ])
// #endregion
// #region balance
diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/ITO.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/ITO.tsx
index d35d84c6d41a..7c5dd5875a20 100644
--- a/packages/mask/src/plugins/ITO/SNSAdaptor/ITO.tsx
+++ b/packages/mask/src/plugins/ITO/SNSAdaptor/ITO.tsx
@@ -29,7 +29,7 @@ import { usePostLink } from '../../../components/DataSource/usePostInfo'
import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton'
import { TokenIcon } from '@masknet/shared'
import { activatedSocialNetworkUI } from '../../../social-network'
-import { getAssetAsBlobURL, getTextUILength, useI18N } from '../../../utils'
+import { getTextUILength, useI18N } from '../../../utils'
import { WalletMessages } from '../../Wallet/messages'
import { ITO_EXCHANGE_RATION_MAX, MSG_DELIMITER, TIME_WAIT_BLOCKCHAIN } from '../constants'
import { sortTokens } from './helpers'
@@ -39,6 +39,7 @@ import { useDestructCallback } from './hooks/useDestructCallback'
import { useIfQualified } from './hooks/useIfQualified'
import { usePoolTradeInfo } from './hooks/usePoolTradeInfo'
import { checkRegionRestrict, decodeRegionCode, useIPRegion } from './hooks/useRegion'
+import { usePoolBackground } from './hooks/usePoolBackground'
import { ITO_Status, JSON_PayloadInMask } from '../types'
import { StyledLinearProgress } from './StyledLinearProgress'
import { SwapGuide, SwapStatus } from './SwapGuide'
@@ -233,7 +234,7 @@ export function ITO(props: ITO_Props) {
const [claimDialogStatus, setClaimDialogStatus] = useState(SwapStatus.Remind)
// assets
- const PoolBackground = getAssetAsBlobURL(new URL('../assets/pool-background.jpg', import.meta.url))
+ const { value: PoolBackground } = usePoolBackground()
const { pid, payload } = props
const { regions: defaultRegions = '-' } = props.payload
@@ -493,7 +494,7 @@ export function ITO(props: ITO_Props) {
tradeInfo?.buyInfo?.token.symbol,
])
- const footerStartTime = useMemo(() => {
+ const FooterStartTime = useMemo(() => {
return (
{t('plugin_ito_list_start_date', { date: formatDateTime(startTime, 'yyyy-MM-dd HH:mm') })}
@@ -501,7 +502,7 @@ export function ITO(props: ITO_Props) {
)
}, [startTime])
- const footerEndTime = useMemo(
+ const FooterEndTime = useMemo(
() => (
{t('plugin_ito_swap_end_date', { date: formatDateTime(endTime, 'yyyy-MM-dd HH:mm') })}
@@ -510,13 +511,13 @@ export function ITO(props: ITO_Props) {
[endTime, t],
)
- const footerSwapInfo = useMemo(
+ const FooterSwapInfo = useMemo(
() => (
<>
{swapResultText}
- {footerEndTime}
+ {FooterEndTime}
{hasLockTime &&
!isUnlocked &&
unlockTime > Date.now() &&
@@ -529,10 +530,10 @@ export function ITO(props: ITO_Props) {
) : null}
>
),
- [footerEndTime, swapResultText],
+ [FooterEndTime, swapResultText],
)
- const footerNormal = useMemo(
+ const FooterNormal = useMemo(
() => (
<>
@@ -543,13 +544,87 @@ export function ITO(props: ITO_Props) {
{listOfStatus.includes(ITO_Status.waited)
- ? footerStartTime
+ ? FooterStartTime
: listOfStatus.includes(ITO_Status.started)
- ? footerEndTime
+ ? FooterEndTime
: null}
>
),
- [footerEndTime, footerStartTime, limit, listOfStatus, token.decimals, token.symbol],
+ [FooterEndTime, FooterStartTime, limit, listOfStatus, token.decimals, token.symbol],
+ )
+
+ const FooterBuyerLockedButton = useMemo(() => {
+ if (!availability?.claimed) {
+ return (
+
+ {claimState.type === TransactionStateType.HASH ? t('plugin_ito_claiming') : t('plugin_ito_claim')}
+
+ )
+ }
+
+ if (canWithdraw) {
+ return (
+
+ {t('plugin_ito_withdraw')}
+
+ )
+ }
+ return null
+ }, [availability?.claimed, canWithdraw, claimState])
+
+ const FooterBuyerWithLockTimeButton = useMemo(
+ () => (
+
+ {(() => {
+ if (isUnlocked) return FooterBuyerLockedButton
+
+ return (
+ undefined}
+ variant="contained"
+ disabled
+ size="large"
+ className={classNames(classes.actionButton, classes.textInOneLine)}>
+ {t('plugin_ito_claim')}
+
+ )
+ })()}
+
+ ),
+ [noRemain, listOfStatus, isUnlocked],
+ )
+
+ const FooterBuyerButton = useMemo(
+ () => (
+
+ {(() => {
+ if (hasLockTime)
+ return (
+
+ {FooterBuyerWithLockTimeButton}
+
+ )
+ if (canWithdraw) {
+ return (
+
+ {t('plugin_ito_withdraw')}
+
+ )
+ }
+ return null
+ })()}
+
+ ),
+ [hasLockTime, canWithdraw],
)
return (
@@ -611,10 +686,10 @@ export function ITO(props: ITO_Props) {
{isBuyer
- ? footerSwapInfo
+ ? FooterSwapInfo
: listOfStatus.includes(ITO_Status.expired)
- ? footerEndTime
- : footerNormal}
+ ? FooterEndTime
+ : FooterNormal}
From: @{sellerName}
@@ -623,163 +698,147 @@ export function ITO(props: ITO_Props) {
- {loadingRegion && isRegionRestrict ? null : !isRegionAllow ? (
- undefined}
- variant="contained"
- size="large"
- className={classes.actionButton}>
- {t('plugin_ito_region_ban')}
-
- ) : (noRemain || listOfStatus.includes(ITO_Status.expired)) &&
- !canWithdraw &&
- ((availability?.claimed && hasLockTime) || !hasLockTime) ? null : loadingTradeInfo ||
- loadingAvailability ? (
- undefined}
- variant="contained"
- size="large"
- className={classes.actionButton}>
- {t('plugin_ito_loading')}
-
- ) : !account || !chainIdValid ? (
-
- {t('plugin_wallet_connect_a_wallet')}
-
- ) : isBuyer ? (
-
- {hasLockTime ? (
-
- {isUnlocked ? (
- !availability?.claimed ? (
-
- {claimState.type === TransactionStateType.HASH
- ? t('plugin_ito_claiming')
- : t('plugin_ito_claim')}
-
- ) : canWithdraw ? (
-
- {t('plugin_ito_withdraw')}
-
- ) : null
- ) : (
- undefined}
- variant="contained"
- disabled
- size="large"
- className={classNames(classes.actionButton, classes.textInOneLine)}>
- {t('plugin_ito_claim')}
-
- )}
-
- ) : canWithdraw ? (
-
-
- {t('plugin_ito_withdraw')}
-
-
- ) : null}
- {noRemain || listOfStatus.includes(ITO_Status.expired) ? null : (
-
-
- {t('plugin_ito_share')}
-
-
- )}
-
- ) : canWithdraw ? (
-
- {t('plugin_ito_withdraw')}
-
- ) : (!ifQualified || !(ifQualified as Qual_V2).qualified) &&
- !isNativeTokenAddress(qualificationAddress) ? (
-
- {loadingIfQualified
- ? t('plugin_ito_qualification_loading')
- : !ifQualified
- ? t('plugin_ito_qualification_failed')
- : !(ifQualified as Qual_V2).qualified
- ? startCase((ifQualified as Qual_V2).errorMsg)
- : null}
-
- ) : listOfStatus.includes(ITO_Status.expired) ? null : listOfStatus.includes(ITO_Status.waited) ? (
-
-
+ {(() => {
+ if (loadingRegion && isRegionRestrict) return null
+
+ if (!isRegionAllow) {
+ return (
undefined}
variant="contained"
size="large"
className={classes.actionButton}>
- {t('plugin_ito_unlock_in_advance')}
+ {t('plugin_ito_region_ban')}
-
- {shareText ? (
-
-
- {t('plugin_ito_share')}
-
-
- ) : undefined}
-
- ) : listOfStatus.includes(ITO_Status.started) ? (
-
-
+ )
+ }
+
+ if (
+ (noRemain || listOfStatus.includes(ITO_Status.expired)) &&
+ !canWithdraw &&
+ ((availability?.claimed && hasLockTime) || !hasLockTime)
+ ) {
+ return null
+ }
+
+ if (loadingTradeInfo || loadingAvailability) {
+ return (
undefined}
variant="contained"
size="large"
className={classes.actionButton}>
- {t('plugin_ito_enter')}
+ {t('plugin_ito_loading')}
-
-
+ )
+ }
+
+ if (!account || !chainIdValid) {
+ return (
- {t('plugin_ito_share')}
+ {t('plugin_wallet_connect_a_wallet')}
-
-
- ) : null}
+ )
+ }
+
+ if (isBuyer) return FooterBuyerButton
+
+ if (canWithdraw) {
+ return (
+
+ {t('plugin_ito_withdraw')}
+
+ )
+ }
+
+ if (
+ (!ifQualified || !(ifQualified as Qual_V2).qualified) &&
+ !isNativeTokenAddress(qualificationAddress)
+ ) {
+ return (
+
+ {loadingIfQualified
+ ? t('plugin_ito_qualification_loading')
+ : !ifQualified
+ ? t('plugin_ito_qualification_failed')
+ : !(ifQualified as Qual_V2).qualified
+ ? startCase((ifQualified as Qual_V2).errorMsg)
+ : null}
+
+ )
+ }
+
+ if (listOfStatus.includes(ITO_Status.expired)) return null
+
+ if (listOfStatus.includes(ITO_Status.waited)) {
+ return (
+
+
+
+ {t('plugin_ito_unlock_in_advance')}
+
+
+ {shareText ? (
+
+
+ {t('plugin_ito_share')}
+
+
+ ) : undefined}
+
+ )
+ }
+
+ if (listOfStatus.includes(ITO_Status.started)) {
+ return (
+
+
+
+ {t('plugin_ito_enter')}
+
+
+
+
+ {t('plugin_ito_share')}
+
+
+
+ )
+ }
+
+ return null
+ })()}
@@ -819,7 +878,7 @@ export function ITO_Loading() {
export function ITO_Error({ retryPoolPayload }: { retryPoolPayload: () => void }) {
const { t } = useI18N()
const { classes } = useStyles({})
- const PoolBackground = getAssetAsBlobURL(new URL('../assets/pool-loading-background.jpg', import.meta.url))
+ const { value: PoolBackground } = usePoolBackground()
return (
{
- if (ev.open || !ev.token || ev.uuid !== id) return
- onTokenChange(ev.token)
- },
- [id, onTokenChange],
- ),
- )
- const onSelectTokenChipClick = useCallback(() => {
- setSelectTokenDialog({
- open: true,
- uuid: id,
+ const pickToken = usePickToken()
+ const onSelectTokenChipClick = useCallback(async () => {
+ const picked = await pickToken({
disableNativeToken,
disableSearchBar,
- FungibleTokenListProps,
+ ...FungibleTokenListProps,
})
- }, [id, disableNativeToken, disableSearchBar, FungibleTokenListProps])
+ if (picked) onTokenChange(picked)
+ }, [disableNativeToken, disableSearchBar, FungibleTokenListProps])
// #endregion
return (
diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/ShareDialog.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/ShareDialog.tsx
index 432658ea1c99..854ceddae0ef 100644
--- a/packages/mask/src/plugins/ITO/SNSAdaptor/ShareDialog.tsx
+++ b/packages/mask/src/plugins/ITO/SNSAdaptor/ShareDialog.tsx
@@ -6,7 +6,8 @@ import type { BigNumber } from 'bignumber.js'
import { useCallback } from 'react'
import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton'
import { activatedSocialNetworkUI } from '../../../social-network'
-import { getAssetAsBlobURL, useI18N } from '../../../utils'
+import { useI18N } from '../../../utils'
+import { usePoolBackground } from './hooks/usePoolBackground'
const useStyles = makeStyles()((theme) => ({
shareWrapper: {
@@ -62,7 +63,7 @@ export interface ShareDialogProps extends withClasses<'root'> {
}
export function ShareDialog(props: ShareDialogProps) {
- const ShareBackground = getAssetAsBlobURL(new URL('../assets/share-background.jpg', import.meta.url))
+ const { value: ShareBackground } = usePoolBackground()
const { t } = useI18N()
const classes = useStylesExtends(useStyles(), {})
const { token, actualSwapAmount, shareSuccessText, onClose } = props
diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/SwapDialog.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/SwapDialog.tsx
index 0863d75fc0b1..de1be2f70f98 100644
--- a/packages/mask/src/plugins/ITO/SNSAdaptor/SwapDialog.tsx
+++ b/packages/mask/src/plugins/ITO/SNSAdaptor/SwapDialog.tsx
@@ -1,35 +1,35 @@
-import { useCallback, useEffect, useMemo, useState } from 'react'
-import BigNumber from 'bignumber.js'
-import { v4 as uuid } from 'uuid'
-import { CircularProgress, Slider, Typography } from '@mui/material'
+import { openWindow, useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { usePickToken } from '@masknet/shared'
import { makeStyles, useStylesExtends } from '@masknet/theme'
-import { useI18N } from '../../../utils'
-import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
-import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton'
+import { leftShift, rightShift, ZERO } from '@masknet/web3-shared-base'
import {
ChainId,
currySameAddress,
EthereumTokenType,
formatBalance,
FungibleTokenDetailed,
+ isNativeTokenAddress,
+ isSameAddress,
resolveTransactionLinkOnExplorer,
TransactionStateType,
useChainId,
useFungibleTokenBalance,
useFungibleTokenDetailed,
- isSameAddress,
useTokenConstants,
- isNativeTokenAddress,
} from '@masknet/web3-shared-evm'
-import { leftShift, rightShift, ZERO } from '@masknet/web3-shared-base'
-import { SelectTokenDialogEvent, WalletMessages, WalletRPC } from '../../Wallet/messages'
-import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel'
-import { useSwapCallback } from './hooks/useSwapCallback'
-import type { JSON_PayloadInMask } from '../types'
-import { SwapStatus } from './SwapGuide'
+import { CircularProgress, Slider, Typography } from '@mui/material'
+import BigNumber from 'bignumber.js'
+import { useCallback, useEffect, useMemo, useState } from 'react'
+import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton'
+import { useI18N } from '../../../utils'
import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary'
import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary'
+import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel'
+import { WalletMessages, WalletRPC } from '../../Wallet/messages'
+import type { JSON_PayloadInMask } from '../types'
import { useQualificationVerify } from './hooks/useQualificationVerify'
+import { useSwapCallback } from './hooks/useSwapCallback'
+import { SwapStatus } from './SwapGuide'
const useStyles = makeStyles()((theme) => ({
button: {
@@ -138,46 +138,25 @@ export function SwapDialog(props: SwapDialogProps) {
swapAmount.isZero() ? '' : formatBalance(swapAmount, swapToken?.decimals),
)
// #region select token
- const [id] = useState(uuid())
- const { setDialog: setSelectTokenDialog } = useRemoteControlledDialog(
- WalletMessages.events.selectTokenDialogUpdated,
- useCallback(
- (ev: SelectTokenDialogEvent) => {
- if (ev.open || !ev.token || ev.uuid !== id) return
- const at = exchangeTokens.findIndex(currySameAddress(ev.token!.address))
- const ratio = new BigNumber(payload.exchange_amounts[at * 2]).dividedBy(
- payload.exchange_amounts[at * 2 + 1],
- )
- setRatio(ratio)
- setSwapToken(ev.token)
- setTokenAmount(initAmount)
- setSwapAmount(initAmount.multipliedBy(ratio))
- setInputAmountForUI(
- initAmount.isZero() ? '' : formatBalance(initAmount.multipliedBy(ratio), ev.token.decimals),
- )
- },
- [
- id,
- payload,
- initAmount,
- exchangeTokens
- .map((x) => x.address)
- .sort()
- .join(),
- ],
- ),
- )
- const onSelectTokenChipClick = useCallback(() => {
- setSelectTokenDialog({
- open: true,
- uuid: id,
+ const pickToken = usePickToken()
+ const onSelectTokenChipClick = useCallback(async () => {
+ const picked = await pickToken({
disableNativeToken: !exchangeTokens.some(isNativeTokenAddress),
disableSearchBar: true,
- FungibleTokenListProps: {
- whitelist: exchangeTokens.map((x) => x.address),
- },
+ whitelist: exchangeTokens.map((x) => x.address),
})
+ if (!picked) return
+ const at = exchangeTokens.findIndex(currySameAddress(picked.address))
+ const ratio = new BigNumber(payload.exchange_amounts[at * 2]).dividedBy(payload.exchange_amounts[at * 2 + 1])
+ setRatio(ratio)
+ setSwapToken(picked)
+ setTokenAmount(initAmount)
+ setSwapAmount(initAmount.multipliedBy(ratio))
+ setInputAmountForUI(initAmount.isZero() ? '' : formatBalance(initAmount.multipliedBy(ratio), picked.decimals))
}, [
+ initAmount,
+ payload,
+ pickToken,
exchangeTokens
.map((x) => x.address)
.sort()
@@ -239,7 +218,7 @@ export function SwapDialog(props: SwapDialogProps) {
if (swapState.type === TransactionStateType.HASH) {
const { hash } = swapState
setTimeout(() => {
- window.open(resolveTransactionLinkOnExplorer(chainId, hash), '_blank', 'noopener noreferrer')
+ openWindow(resolveTransactionLinkOnExplorer(chainId, hash))
}, 2000)
return
}
diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/SwapGuide.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/SwapGuide.tsx
index 962a662858a1..429c546fcb71 100644
--- a/packages/mask/src/plugins/ITO/SNSAdaptor/SwapGuide.tsx
+++ b/packages/mask/src/plugins/ITO/SNSAdaptor/SwapGuide.tsx
@@ -4,7 +4,7 @@ import { DialogContent } from '@mui/material'
import { makeStyles } from '@masknet/theme'
import BigNumber from 'bignumber.js'
import { useCallback, useEffect, useMemo, useState, useTransition } from 'react'
-import { InjectedDialog, InjectedDialogProps } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog, InjectedDialogProps } from '@masknet/shared'
import { useI18N } from '../../../utils'
import { RemindDialog } from './RemindDialog'
import { ShareDialog } from './ShareDialog'
diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/UnlockDialog.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/UnlockDialog.tsx
index c2ba6a47acf5..39afec2f26fe 100644
--- a/packages/mask/src/plugins/ITO/SNSAdaptor/UnlockDialog.tsx
+++ b/packages/mask/src/plugins/ITO/SNSAdaptor/UnlockDialog.tsx
@@ -1,12 +1,7 @@
-import { Link, Typography } from '@mui/material'
import { makeStyles } from '@masknet/theme'
+import { isGreaterThan, rightShift } from '@masknet/web3-shared-base'
import { useCallback, useState } from 'react'
-import { v4 as uuid } from 'uuid'
-import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
-import { useI18N } from '../../../utils'
-import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton'
import {
- useITOConstants,
ERC20TokenDetailed,
EthereumTokenType,
formatBalance,
@@ -14,13 +9,16 @@ import {
resolveAddressLinkOnExplorer,
useChainId,
useFungibleTokenBalance,
+ useITOConstants,
} from '@masknet/web3-shared-evm'
-import { isGreaterThan, rightShift } from '@masknet/web3-shared-base'
+import { Link, Typography } from '@mui/material'
+import { Trans } from 'react-i18next'
+import { usePickToken } from '@masknet/shared'
+import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton'
+import { useI18N } from '../../../utils'
import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary'
import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary'
import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel'
-import { SelectTokenDialogEvent, WalletMessages } from '../../Wallet/messages'
-import { Trans } from 'react-i18next'
function isMoreThanMillion(allowance: string, decimals: number) {
return isGreaterThan(allowance, `100000000000e${decimals}`) // 100 billion
@@ -51,30 +49,16 @@ export function UnlockDialog(props: UnlockDialogProps) {
// #region select token
const [token, setToken] = useState(tokens[0])
- const [id] = useState(uuid())
- const { setDialog: setSelectTokenDialog } = useRemoteControlledDialog(
- WalletMessages.events.selectTokenDialogUpdated,
- useCallback(
- (ev: SelectTokenDialogEvent) => {
- if (ev.open || !ev.token || ev.uuid !== id) return
- if (ev.token.type !== EthereumTokenType.ERC20) return
- setToken(ev.token)
- },
- [id],
- ),
- )
- const onSelectTokenChipClick = useCallback(() => {
- setSelectTokenDialog({
- open: true,
- uuid: id,
+ const pickToken = usePickToken()
+ const onSelectTokenChipClick = useCallback(async () => {
+ const picked = await pickToken({
disableNativeToken: true,
disableSearchBar: true,
- FungibleTokenListProps: {
- selectedTokens: token ? [token.address] : [],
- whitelist: tokens.map((x) => x.address),
- },
+ selectedTokens: token?.address ? [token.address] : [],
+ whitelist: tokens.map((x) => x.address),
})
- }, [id, token?.address])
+ if (picked) setToken(picked as ERC20TokenDetailed)
+ }, [tokens, token?.address])
// #endregion
// #region amount
const [rawAmount, setRawAmount] = useState('')
diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/hooks/usePoolBackground.ts b/packages/mask/src/plugins/ITO/SNSAdaptor/hooks/usePoolBackground.ts
new file mode 100644
index 000000000000..8b40e0b945aa
--- /dev/null
+++ b/packages/mask/src/plugins/ITO/SNSAdaptor/hooks/usePoolBackground.ts
@@ -0,0 +1,6 @@
+import { useAsync } from 'react-use'
+import { getAssetAsBlobURL } from '../../../../utils'
+
+export function usePoolBackground() {
+ return useAsync(async () => getAssetAsBlobURL(new URL('../../assets/pool-background.jpg', import.meta.url)), [])
+}
diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/index.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/index.tsx
index a1e82a69ff96..d88bf44cac20 100644
--- a/packages/mask/src/plugins/ITO/SNSAdaptor/index.tsx
+++ b/packages/mask/src/plugins/ITO/SNSAdaptor/index.tsx
@@ -1,4 +1,5 @@
import { Plugin, usePluginWrapper } from '@masknet/plugin-infra'
+import { useState } from 'react'
import { ItoLabelIcon } from '../assets/ItoLabelIcon'
import { makeStyles } from '@masknet/theme'
import {
@@ -16,6 +17,9 @@ import { CompositionDialog } from './CompositionDialog'
import { set } from 'lodash-unified'
import { EthereumChainBoundary } from '../../../web3/UI/EthereumChainBoundary'
import { MarketsIcon } from '@masknet/icons'
+import { ApplicationEntry } from '@masknet/shared'
+import { CrossIsolationMessages } from '@masknet/shared-base'
+import { ClaimAllDialog } from './ClaimAllDialog'
const useStyles = makeStyles()((theme) => ({
root: {
@@ -57,6 +61,46 @@ const sns: Plugin.SNSAdaptor.Definition = {
),
},
},
+ ApplicationEntries: [
+ {
+ RenderEntryComponent({ disabled }) {
+ return (
+
+ CrossIsolationMessages.events.requestComposition.sendToLocal({
+ reason: 'timeline',
+ open: true,
+ options: {
+ startupPlugin: base.ID,
+ },
+ })
+ }
+ />
+ )
+ },
+ defaultSortingPriority: 3,
+ },
+ {
+ RenderEntryComponent({ disabled }) {
+ const [open, setOpen] = useState(false)
+ return (
+ <>
+ setOpen(true)}
+ />
+ setOpen(false)} />
+ >
+ )
+ },
+ defaultSortingPriority: 4,
+ },
+ ],
}
function onAttached_ITO(payload: JSON_PayloadComposeMask) {
diff --git a/packages/mask/src/components/shared/assets/gift.png b/packages/mask/src/plugins/ITO/assets/gift.png
similarity index 100%
rename from packages/mask/src/components/shared/assets/gift.png
rename to packages/mask/src/plugins/ITO/assets/gift.png
diff --git a/packages/mask/src/components/shared/assets/token.png b/packages/mask/src/plugins/ITO/assets/token.png
similarity index 100%
rename from packages/mask/src/components/shared/assets/token.png
rename to packages/mask/src/plugins/ITO/assets/token.png
diff --git a/packages/mask/src/plugins/ITO/base.ts b/packages/mask/src/plugins/ITO/base.ts
index eceee4b13b46..272f230febbc 100644
--- a/packages/mask/src/plugins/ITO/base.ts
+++ b/packages/mask/src/plugins/ITO/base.ts
@@ -12,7 +12,10 @@ export const base: Plugin.Shared.Definition = {
publisher: { name: { fallback: 'Mask Network' }, link: 'https://mask.io/' },
enableRequirement: {
architecture: { app: true, web: true },
- networks: { type: 'opt-out', networks: {} },
+ networks: {
+ type: 'opt-out',
+ networks: {},
+ },
target: 'stable',
web3: {
[NetworkPluginID.PLUGIN_EVM]: {
@@ -27,6 +30,8 @@ export const base: Plugin.Shared.Definition = {
ChainId.Fantom,
],
},
+ [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] },
+ [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] },
},
},
contribution: { metadataKeys: new Set([ITO_MetaKey_1, ITO_MetaKey_2]) },
diff --git a/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawDialog.tsx b/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawDialog.tsx
index 3175cd46946c..3b54781299a1 100644
--- a/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawDialog.tsx
+++ b/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawDialog.tsx
@@ -3,7 +3,7 @@ import { useContainer } from 'unstated-next'
import { makeStyles } from '@masknet/theme'
import { Add, Remove } from '@mui/icons-material'
import { useProviderDescriptor } from '@masknet/plugin-infra'
-import { FormattedAddress, FormattedBalance, ImageIcon } from '@masknet/shared'
+import { FormattedAddress, FormattedBalance, ImageIcon, InjectedDialog } from '@masknet/shared'
import { Box, Button, DialogContent, TextField, Typography } from '@mui/material'
import {
formatBalance,
@@ -13,7 +13,6 @@ import {
useMaskBoxConstants,
EthereumTokenType,
} from '@masknet/web3-shared-evm'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
import ActionButton from '../../../../extension/options-page/DashboardComponents/ActionButton'
import { EthereumERC20TokenApprovedBoundary } from '../../../../web3/UI/EthereumERC20TokenApprovedBoundary'
import { EthereumWalletConnectedBoundary } from '../../../../web3/UI/EthereumWalletConnectedBoundary'
diff --git a/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawResultDialog.tsx b/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawResultDialog.tsx
index efc34c74250e..cbcd770a0b99 100644
--- a/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawResultDialog.tsx
+++ b/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawResultDialog.tsx
@@ -5,7 +5,7 @@ import { Box, DialogContent } from '@mui/material'
import type { ERC721ContractDetailed } from '@masknet/web3-shared-evm'
import type { BoxInfo } from '../../type'
import { TokenCard } from './TokenCard'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import ActionButton from '../../../../extension/options-page/DashboardComponents/ActionButton'
import { activatedSocialNetworkUI } from '../../../../social-network'
import { usePostLink } from '../../../../components/DataSource/usePostInfo'
diff --git a/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx b/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx
index 7fa3108da7bb..a4ef2807ea8a 100644
--- a/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx
+++ b/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx
@@ -6,6 +6,8 @@ import { parseURL } from '@masknet/shared-base'
import { EthereumChainBoundary } from '../../../web3/UI/EthereumChainBoundary'
import { PreviewCard } from './components/PreviewCard'
import { Context } from '../hooks/useContext'
+import { ApplicationEntry } from '@masknet/shared'
+import { openWindow } from '@masknet/shared-base-ui'
const isMaskBox = (x: string) => x.startsWith('https://box-beta.mask.io') || x.startsWith('https://box.mask.io')
@@ -26,6 +28,21 @@ const sns: Plugin.SNSAdaptor.Definition = {
if (!link) return null
return
},
+ ApplicationEntries: [
+ {
+ RenderEntryComponent({ disabled }) {
+ return (
+ openWindow('https://box.mask.io/#/')}
+ />
+ )
+ },
+ defaultSortingPriority: 6,
+ },
+ ],
}
export default sns
diff --git a/packages/mask/src/components/shared/assets/mask_box.png b/packages/mask/src/plugins/MaskBox/assets/mask_box.png
similarity index 100%
rename from packages/mask/src/components/shared/assets/mask_box.png
rename to packages/mask/src/plugins/MaskBox/assets/mask_box.png
diff --git a/packages/mask/src/plugins/NextID/SNSAdaptor/index.tsx b/packages/mask/src/plugins/NextID/SNSAdaptor/index.tsx
index 0df313132cef..f95c8b0dc1fb 100644
--- a/packages/mask/src/plugins/NextID/SNSAdaptor/index.tsx
+++ b/packages/mask/src/plugins/NextID/SNSAdaptor/index.tsx
@@ -1,15 +1,18 @@
import type { Plugin } from '@masknet/plugin-infra'
+import { EMPTY_LIST } from '@masknet/shared-base'
+import { Flags } from '../../../../shared'
import { base } from '../base'
-import { PLUGIN_ID } from '../constants'
import { NextIdPage } from '../components/NextIdPage'
-import { RootContext } from '../contexts'
import { PostTipButton, TipTaskManager } from '../components/Tip'
-import { Flags } from '../../../../shared'
-import { EMPTY_LIST } from '@masknet/web3-shared-evm'
+import { PLUGIN_ID } from '../constants'
+import { RootContext } from '../contexts'
+import { setupStorage, storageDefaultValue } from '../storage'
const sns: Plugin.SNSAdaptor.Definition = {
...base,
- init() {},
+ init(signal, context) {
+ setupStorage(context.createKVStorage('memory', storageDefaultValue))
+ },
ProfileTabs: [
{
ID: `${PLUGIN_ID}_tabContent`,
diff --git a/packages/mask/src/plugins/NextID/base.ts b/packages/mask/src/plugins/NextID/base.ts
index 14d2791bcfce..832aeb38641c 100644
--- a/packages/mask/src/plugins/NextID/base.ts
+++ b/packages/mask/src/plugins/NextID/base.ts
@@ -31,6 +31,8 @@ export const base: Plugin.Shared.Definition = {
ChainId.Conflux,
],
},
+ [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] },
+ [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] },
},
},
experimentalMark: true,
diff --git a/packages/mask/src/plugins/NextID/components/BindDialog.tsx b/packages/mask/src/plugins/NextID/components/BindDialog.tsx
index 525e608bc341..4879f7f3e952 100644
--- a/packages/mask/src/plugins/NextID/components/BindDialog.tsx
+++ b/packages/mask/src/plugins/NextID/components/BindDialog.tsx
@@ -12,7 +12,8 @@ import { delay } from '@dimensiondev/kit'
import { useBindPayload } from '../hooks/useBindPayload'
import { usePersonaSign } from '../hooks/usePersonaSign'
import { useWalletSign } from '../hooks/useWalletSign'
-import { bindProof } from '@masknet/web3-providers'
+import { NextIDProof } from '@masknet/web3-providers'
+import { MaskMessages } from '../../../../shared'
interface BindDialogProps {
open: boolean
@@ -36,7 +37,7 @@ export const BindDialog = memo(({ open, onClose, persona, onBou
useAsyncRetry(async () => {
if (!personaSignState.value || !walletSignState.value || isBound || !message || !persona.publicHexKey) return
try {
- await bindProof(
+ await NextIDProof.bindProof(
message.uuid,
persona.publicHexKey,
NextIDAction.Create,
@@ -52,6 +53,9 @@ export const BindDialog = memo(({ open, onClose, persona, onBou
variant: 'success',
message: t.notify_wallet_sign_request_success(),
})
+
+ MaskMessages.events.ownProofChanged.sendToAll()
+
await delay(2000)
onBound()
onClose()
diff --git a/packages/mask/src/plugins/NextID/components/BindPanelUI.tsx b/packages/mask/src/plugins/NextID/components/BindPanelUI.tsx
index 56e9cbe617c2..886164d5a15e 100644
--- a/packages/mask/src/plugins/NextID/components/BindPanelUI.tsx
+++ b/packages/mask/src/plugins/NextID/components/BindPanelUI.tsx
@@ -7,9 +7,9 @@ import DoneIcon from '@mui/icons-material/Done'
import { useI18N } from '../locales'
import { getMaskColor, makeStyles, MaskColorVar } from '@masknet/theme'
import type { Persona } from '../../../database'
-import { formatFingerprint, LoadingAnimation } from '@masknet/shared'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
-import { NetworkPluginID, usePluginIDContext } from '@masknet/plugin-infra'
+import { InjectedDialog, LoadingAnimation } from '@masknet/shared'
+import { NetworkPluginID, useCurrentWeb3NetworkPluginID } from '@masknet/plugin-infra'
+import { formatPersonaFingerprint } from '@masknet/shared-base'
const useStyles = makeStyles()((theme) => ({
persona: {
@@ -93,7 +93,7 @@ export const BindPanelUI = memo(
({ onPersonaSign, onWalletSign, currentPersona, signature, isBound, title, onClose, open, isCurrentAccount }) => {
const t = useI18N()
const { classes } = useStyles()
- const pluginId = usePluginIDContext()
+ const pluginId = useCurrentWeb3NetworkPluginID()
const isSupported = SUPPORTED_PLUGINS.includes(pluginId)
const isWalletSigned = !!signature.wallet.value
@@ -162,7 +162,7 @@ export const BindPanelUI = memo(
{currentPersona?.nickname}
- {formatFingerprint(currentPersona?.identifier.compressedPoint ?? '', 10)}
+ {formatPersonaFingerprint(currentPersona?.identifier.compressedPoint ?? '', 10)}
diff --git a/packages/mask/src/plugins/NextID/components/ConfirmModal.tsx b/packages/mask/src/plugins/NextID/components/ConfirmModal.tsx
index 2e619b520f55..bae9341cd67d 100644
--- a/packages/mask/src/plugins/NextID/components/ConfirmModal.tsx
+++ b/packages/mask/src/plugins/NextID/components/ConfirmModal.tsx
@@ -1,7 +1,7 @@
import { makeStyles } from '@masknet/theme'
import { Button, DialogActions, DialogContent, Typography } from '@mui/material'
import type { FC, ReactNode } from 'react'
-import { InjectedDialog, InjectedDialogProps } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog, InjectedDialogProps } from '@masknet/shared'
const useStyles = makeStyles()((theme) => ({
confirmDialog: {
diff --git a/packages/mask/src/plugins/NextID/components/NextIdPage.tsx b/packages/mask/src/plugins/NextID/components/NextIdPage.tsx
index 12a63395ffb9..24232623a75b 100644
--- a/packages/mask/src/plugins/NextID/components/NextIdPage.tsx
+++ b/packages/mask/src/plugins/NextID/components/NextIdPage.tsx
@@ -1,6 +1,6 @@
import { NextIDPlatform } from '@masknet/shared-base'
import { makeStyles } from '@masknet/theme'
-import { queryExistedBindingByPersona, queryIsBound } from '@masknet/web3-providers'
+import { NextIDProof } from '@masknet/web3-providers'
import { Box, Button, Skeleton, Stack, Typography } from '@mui/material'
import { useMemo, useState } from 'react'
import { useAsync, useAsyncRetry } from 'react-use'
@@ -58,12 +58,13 @@ export function NextIdPage({ personaList }: NextIDPageProps) {
const currentProfileIdentifier = useLastRecognizedIdentity()
const visitingPersonaIdentifier = useCurrentVisitingIdentity()
const personaConnectStatus = usePersonaConnectStatus()
- const { reset, isVerified, action } = useNextIDConnectStatus()
+ const { reset, isVerified } = useNextIDConnectStatus()
const [openBindDialog, toggleBindDialog] = useState(false)
const [unbindAddress, setUnBindAddress] = useState()
const platform = activatedSocialNetworkUI.configuration.nextIDConfig?.platform as NextIDPlatform
const isOwn = currentProfileIdentifier.identifier.toText() === visitingPersonaIdentifier.identifier.toText()
+ const tipable = !isOwn
const personaActionButton = useMemo(() => {
if (!personaConnectStatus.action) return null
@@ -83,7 +84,11 @@ export function NextIdPage({ personaList }: NextIDPageProps) {
const { value: isAccountVerified, loading: loadingVerifyInfo } = useAsync(async () => {
if (!currentPersona?.publicHexKey) return
- return queryIsBound(currentPersona.publicHexKey, platform, visitingPersonaIdentifier.identifier.userId)
+ return NextIDProof.queryIsBound(
+ currentPersona.publicHexKey,
+ platform,
+ visitingPersonaIdentifier.identifier.userId,
+ )
}, [isOwn, currentPersona, visitingPersonaIdentifier, isVerified])
const {
@@ -92,7 +97,7 @@ export function NextIdPage({ personaList }: NextIDPageProps) {
retry: retryQueryBinding,
} = useAsyncRetry(async () => {
if (!currentPersona) return
- return queryExistedBindingByPersona(currentPersona.publicHexKey!)
+ return NextIDProof.queryExistedBindingByPersona(currentPersona.publicHexKey!)
}, [currentPersona, isOwn])
const onVerify = async () => {
@@ -149,7 +154,7 @@ export function NextIdPage({ personaList }: NextIDPageProps) {
{bindings.proofs.map((x) => (
({
+ addButton: {
+ marginLeft: 'auto',
+ },
+ chain: {
+ display: 'flex',
+ },
+ row: {
+ marginTop: theme.spacing(1.5),
+ },
+ chainName: {
+ marginLeft: theme.spacing(1),
+ },
+ error: {
+ display: 'flex',
+ height: 16,
+ alignItems: 'center',
+ color: theme.palette.error.main,
+ '&::before': {
+ backgroundColor: theme.palette.error.main,
+ content: '""',
+ width: 2,
+ height: 16,
+ marginRight: theme.spacing(0.5),
+ },
+ },
+}))
+
+interface Props extends InjectedDialogProps {
+ onAdd?(token: ERC721TokenDetailed): void
+}
+
+export const AddDialog: FC = ({ onAdd, onClose, ...rest }) => {
+ const { classes } = useStyles()
+ const chainId = useChainId()
+ const account = useAccount()
+ const [contractAddress, setContractAddress] = useState('')
+ const [tokenId, setTokenId] = useState('')
+ const t = useI18N()
+ const allNetworks = useNetworkDescriptors()
+ const network = useMemo(() => allNetworks.find((n) => n.chainId === chainId), [allNetworks, chainId])
+ const erc721TokenContract = useERC721TokenContract(contractAddress)
+
+ const [, checkOwner] = useAsyncFn(async () => {
+ if (!erc721TokenContract || !account) return false
+ const ownerAddress = await erc721TokenContract.methods.ownerOf(tokenId).call()
+ return isSameAddress(ownerAddress, account)
+ }, [erc721TokenContract, tokenId, account])
+
+ const [message, setMessage] = useState('')
+ const reset = useCallback(() => {
+ setMessage('')
+ setContractAddress('')
+ setTokenId('')
+ }, [])
+
+ useEffect(() => {
+ setMessage('')
+ }, [tokenId, contractAddress])
+
+ const [state, handleAdd] = useAsyncFn(async () => {
+ if (!erc721TokenContract || !EthereumAddress.isValid(contractAddress)) {
+ setMessage(t.tip_add_collectibles_error())
+ return
+ }
+
+ const hasOwnership = await checkOwner()
+ if (!hasOwnership) {
+ setMessage(t.tip_add_collectibles_error())
+ return
+ }
+ const erc721ContractDetailed = await getERC721ContractDetailed(erc721TokenContract, contractAddress, chainId)
+ const erc721TokenDetailed = await getERC721TokenDetailed(
+ erc721ContractDetailed,
+ erc721TokenContract,
+ tokenId,
+ chainId,
+ )
+
+ if (!erc721TokenDetailed) {
+ setMessage(t.tip_add_collectibles_error())
+ return
+ }
+ await WalletRPC.addToken(erc721TokenDetailed)
+ onAdd?.(erc721TokenDetailed)
+ reset()
+ }, [onAdd, t, contractAddress, tokenId])
+
+ const handleClose = useCallback(() => {
+ onClose?.()
+ reset()
+ }, [onClose])
+
+ const addButton = useMemo(() => {
+ return (
+
+ {state.loading ? t.tip_adding() : t.tip_add()}
+
+ )
+ }, [t, handleAdd, state.loading])
+
+ if (!network) return null
+ return (
+
+
+
+
+ {network.name}
+
+
+ setContractAddress(e.currentTarget.value)}
+ placeholder={t.tip_add_collectibles_contract_address()}
+ />
+
+
+ setTokenId(e.currentTarget.value)}
+ placeholder={t.tip_add_collectibles_token_id()}
+ />
+
+ {message ? (
+
+ {message}
+
+ ) : null}
+
+
+ )
+}
diff --git a/packages/mask/src/plugins/NextID/components/Tip/NFTSection/NFTList.tsx b/packages/mask/src/plugins/NextID/components/Tip/NFTSection/NFTList.tsx
index f19c33b3bcbf..bafa0ba86ed1 100644
--- a/packages/mask/src/plugins/NextID/components/Tip/NFTSection/NFTList.tsx
+++ b/packages/mask/src/plugins/NextID/components/Tip/NFTSection/NFTList.tsx
@@ -1,16 +1,17 @@
+import { useWeb3State, Web3Plugin } from '@masknet/plugin-infra'
import { NFTCardStyledAssetPlayer } from '@masknet/shared'
-import { makeStyles } from '@masknet/theme'
-import { ERC721TokenDetailed, formatNFT_TokenId, useChainId } from '@masknet/web3-shared-evm'
-import { Checkbox, List, ListItem, Radio, Typography } from '@mui/material'
+import { makeStyles, ShadowRootTooltip } from '@masknet/theme'
+import { formatNFT_TokenId, isSameAddress, useChainId } from '@masknet/web3-shared-evm'
+import { Checkbox, Link, List, ListItem, Radio } from '@mui/material'
import classnames from 'classnames'
import { noop } from 'lodash-unified'
import { FC, useCallback } from 'react'
+import type { TipNFTKeyPair } from '../../../types'
interface Props {
- selectedIds: string[]
- tokens: ERC721TokenDetailed[]
- enableTokenIds?: string[]
- onChange?: (ids: string[]) => void
+ selectedPairs: TipNFTKeyPair[]
+ tokens: Web3Plugin.NonFungibleToken[]
+ onChange?: (id: string | null, contractAddress: string) => void
limit?: number
className: string
}
@@ -21,111 +22,157 @@ const useStyles = makeStyles()((theme) => ({
right: 0,
top: 0,
},
+ list: {
+ gridGap: 13,
+ display: 'grid',
+ gridTemplateColumns: 'repeat(5, 1fr)',
+ },
nftItem: {
position: 'relative',
cursor: 'pointer',
- background: theme.palette.mode === 'light' ? '#fff' : '#2F3336',
+ background: theme.palette.mode === 'light' ? '#EDEFEF' : '#2F3336',
display: 'flex',
overflow: 'hidden',
padding: 0,
flexDirection: 'column',
- borderRadius: 8,
- height: 180,
+ borderRadius: 12,
+ height: 100,
userSelect: 'none',
- width: 120,
+ width: 100,
justifyContent: 'center',
},
disabled: {
opacity: 0.5,
cursor: 'not-allowed',
},
+ selected: {
+ position: 'relative',
+ '&::after': {
+ position: 'absolute',
+ border: `2px solid ${theme.palette.primary.main}`,
+ content: '""',
+ left: 0,
+ top: 0,
+ pointerEvents: 'none',
+ boxSizing: 'border-box',
+ width: '100%',
+ height: '100%',
+ borderRadius: 12,
+ },
+ },
+ unselected: {
+ opacity: 0.5,
+ },
loadingFailImage: {
width: 64,
height: 64,
},
+ assetPlayerIframe: {
+ height: 100,
+ width: 100,
+ },
+ imgWrapper: {
+ height: '100px !important',
+ width: '100px !important',
+ img: {
+ height: '100%',
+ width: '100%',
+ },
+ },
+ tooltip: {
+ marginBottom: `${theme.spacing(0.5)} !important`,
+ },
}))
-function arrayRemove(arr: T[], item: T): T[] {
- const idx = arr.indexOf(item)
- return arr.splice(idx, 1)
-}
-
interface NFTItemProps {
- token: ERC721TokenDetailed
+ token: Web3Plugin.NonFungibleToken
}
export const NFTItem: FC = ({ token }) => {
const { classes } = useStyles()
const chainId = useChainId()
return (
- <>
-
-
- {formatNFT_TokenId(token.tokenId, 2)}
-
- >
+
)
}
-export const NFTList: FC = ({ selectedIds, tokens, enableTokenIds = [], onChange, limit = 1, className }) => {
+const includes = (pairs: TipNFTKeyPair[], pair: TipNFTKeyPair): boolean => {
+ return !!pairs.find(([address, tokenId]) => isSameAddress(address, pair[0]) && tokenId === pair[1])
+}
+
+export const NFTList: FC = ({ selectedPairs, tokens, onChange, limit = 1, className }) => {
const { classes } = useStyles()
const isRadio = limit === 1
- const reachedLimit = selectedIds.length >= limit
+ const reachedLimit = selectedPairs.length >= limit
const toggleItem = useCallback(
- (currentId: string) => {
- if (!onChange) return
- if (isRadio) return onChange([currentId])
- let newIds = [...selectedIds]
- if (selectedIds.includes(currentId)) {
- arrayRemove(newIds, currentId)
- } else if (!reachedLimit) {
- newIds = [...selectedIds, currentId]
- }
- onChange(newIds)
+ (currentId: string | null, contractAddress: string) => {
+ onChange?.(currentId, contractAddress)
},
- [selectedIds, onChange, isRadio, reachedLimit],
+ [onChange, isRadio, reachedLimit],
)
const SelectComponent = isRadio ? Radio : Checkbox
+ const { Utils } = useWeb3State()
return (
-
+
{tokens.map((token) => {
- const isNotOwned = !enableTokenIds.includes(token.tokenId)
- const disabled = (!isRadio && reachedLimit && !selectedIds.includes(token.tokenId)) || isNotOwned
+ const selected = includes(selectedPairs, [token.contract?.address!, token.tokenId])
+ const disabled = !isRadio && reachedLimit && !selected
+ const link =
+ Utils?.resolveNonFungibleTokenLink && token.contract
+ ? Utils.resolveNonFungibleTokenLink(
+ token.contract?.chainId,
+ token.contract?.address,
+ token.tokenId,
+ )
+ : undefined
return (
- {
- if (disabled) return
- toggleItem(token.tokenId)
- }}>
-
- {
- if (disabled) return
- toggleItem(token.tokenId)
- }}
- className={classes.checkbox}
- checked={selectedIds.includes(token.tokenId)}
- />
-
+ title={`${token.contract?.name} ${formatNFT_TokenId(token.tokenId, 2)}`}
+ placement="top"
+ arrow>
+ 0 && !selected,
+ })}>
+
+
+
+ {
+ if (disabled || !token.contract?.address) return
+ if (selected) {
+ toggleItem(null, '')
+ } else {
+ toggleItem(token.tokenId, token.contract.address)
+ }
+ }}
+ className={classes.checkbox}
+ checked={selected}
+ />
+
+
)
})}
diff --git a/packages/mask/src/plugins/NextID/components/Tip/NFTSection/index.tsx b/packages/mask/src/plugins/NextID/components/Tip/NFTSection/index.tsx
index 8232f6a1b325..890b18d2701f 100644
--- a/packages/mask/src/plugins/NextID/components/Tip/NFTSection/index.tsx
+++ b/packages/mask/src/plugins/NextID/components/Tip/NFTSection/index.tsx
@@ -1,14 +1,17 @@
+import { useNetworkDescriptor, useWeb3State as useWeb3PluginState } from '@masknet/plugin-infra'
+import { EMPTY_LIST } from '@masknet/shared-base'
import { makeStyles } from '@masknet/theme'
-import { ERC721TokenDetailed, useAccount, useERC721TokenDetailedCallback } from '@masknet/web3-shared-evm'
-import { useERC721TokenDetailedOwnerList } from '@masknet/web3-providers'
-import { Button, FormControl, Typography } from '@mui/material'
+import { isSameAddress, useAccount } from '@masknet/web3-shared-evm'
+import { Button, CircularProgress, Typography } from '@mui/material'
import classnames from 'classnames'
-import { FC, HTMLProps, useCallback, useMemo, useState } from 'react'
-import { SearchInput } from '../../../../../extension/options-page/DashboardComponents/SearchInput'
-import { ERC721ContractSelectPanel } from '../../../../../web3/UI/ERC721ContractSelectPanel'
-import { TargetChainIdContext, useTip } from '../../../contexts'
-import { NFTList } from './NFTList'
+import { uniqWith } from 'lodash-unified'
+import { FC, HTMLProps, useEffect, useMemo, useState } from 'react'
+import { useAsyncFn, useTimeoutFn } from 'react-use'
+import { WalletMessages } from '../../../../Wallet/messages'
+import { useTip } from '../../../contexts'
import { useI18N } from '../../../locales'
+import type { TipNFTKeyPair } from '../../../types'
+import { NFTList } from './NFTList'
export * from './NFTList'
@@ -17,36 +20,28 @@ const useStyles = makeStyles()((theme) => ({
display: 'flex',
flexDirection: 'column',
overflow: 'auto',
+ height: 282,
},
selectSection: {
- marginTop: theme.spacing(1.5),
display: 'flex',
flexDirection: 'column',
overflow: 'auto',
},
+ statusBox: {
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: 282,
+ },
+ loadingText: {
+ marginTop: theme.spacing(1),
+ },
list: {
flexGrow: 1,
- marginTop: theme.spacing(2),
- display: 'grid',
- gridTemplateColumns: 'repeat(4, 1fr)',
maxHeight: 400,
overflow: 'auto',
- gridGap: 18,
- backgroundColor: theme.palette.background.default,
borderRadius: 4,
- padding: theme.spacing(1),
- },
- keyword: {
- borderRadius: 8,
- marginRight: theme.spacing(1.5),
- },
- searchButton: {
- borderRadius: 8,
- width: 100,
- },
- row: {
- display: 'flex',
- flexDirection: 'row',
},
errorMessage: {
marginTop: theme.spacing(3),
@@ -56,74 +51,103 @@ const useStyles = makeStyles()((theme) => ({
},
}))
-interface Props extends HTMLProps {}
+interface Props extends HTMLProps {
+ onAddToken?(): void
+ onEmpty?(empty: boolean): void
+}
-export const NFTSection: FC = ({ className, ...rest }) => {
- const t = useI18N()
- const { targetChainId: chainId } = TargetChainIdContext.useContainer()
- const { erc721Contract, setErc721Contract, erc721TokenId, setErc721TokenId, isSending } = useTip()
- const [tokenId, setTokenId, erc721TokenDetailedCallback] = useERC721TokenDetailedCallback(erc721Contract)
+export const NFTSection: FC = ({ className, onAddToken, onEmpty, ...rest }) => {
+ const { erc721Address, erc721TokenId, setErc721TokenId, setErc721Address } = useTip()
const { classes } = useStyles()
+ const t = useI18N()
const account = useAccount()
- const { tokenDetailedOwnerList: myTokens = [] } = useERC721TokenDetailedOwnerList(erc721Contract, account)
+ const selectedPairs: TipNFTKeyPair[] = useMemo(
+ () => (erc721Address && erc721TokenId ? [[erc721Address, erc721TokenId]] : []),
+ [erc721TokenId, erc721TokenId],
+ )
+ const { Asset } = useWeb3PluginState()
+
+ const networkDescriptor = useNetworkDescriptor()
+
+ // Cannot get the loading status of fetching via websocket
+ // loading status of `useAsyncRetry` is not the real status
+ const [guessLoading, setGuessLoading] = useState(true)
+ useTimeoutFn(() => {
+ setGuessLoading(false)
+ }, 10000)
+
+ const [{ value = { data: EMPTY_LIST }, loading }, fetchTokens] = useAsyncFn(async () => {
+ const result = await Asset?.getNonFungibleAssets?.(account, { page: 0 }, undefined, networkDescriptor)
+ return result
+ }, [account, Asset?.getNonFungibleAssets, networkDescriptor])
+
+ useEffect(() => {
+ fetchTokens()
+ }, [fetchTokens])
+
+ useEffect(() => {
+ const unsubscribeTokens = WalletMessages.events.erc721TokensUpdated.on(fetchTokens)
+ const unsubscribeSocket = WalletMessages.events.socketMessageUpdated.on((info) => {
+ setGuessLoading(info.done)
+ if (!info.done) {
+ fetchTokens()
+ }
+ })
+ return () => {
+ unsubscribeTokens()
+ unsubscribeSocket()
+ }
+ }, [fetchTokens])
+
+ const fetchedTokens = value?.data ?? EMPTY_LIST
- const selectedIds = useMemo(() => (erc721TokenId ? [erc721TokenId] : []), [erc721TokenId])
+ const tokens = useMemo(() => {
+ return uniqWith(fetchedTokens, (v1, v2) => {
+ return isSameAddress(v1.contract?.address, v2.contract?.address) && v1.tokenId === v2.tokenId
+ })
+ }, [fetchedTokens])
- const [searchedToken, setSearchedToken] = useState(null)
- const onSearch = useCallback(async () => {
- const token = await erc721TokenDetailedCallback()
- setSearchedToken(token?.info.owner ? token : null)
- }, [erc721TokenDetailedCallback])
+ const showLoadingIndicator = tokens.length === 0 && !loading && !guessLoading
- const tokens = useMemo(() => (searchedToken ? [searchedToken] : myTokens), [searchedToken, myTokens])
- const enableTokenIds = useMemo(() => myTokens.map((t) => t.tokenId), [myTokens])
+ useEffect(() => {
+ onEmpty?.(showLoadingIndicator)
+ }, [onEmpty, showLoadingIndicator])
return (
-
-
-
- {erc721Contract ? (
-
-
- setTokenId(id)}
- inputBaseProps={{
- disabled: isSending,
- }}
- label=""
- />
-
- {t.search()}
-
-
- {
- setErc721TokenId(ids.length ? ids[0] : null)
- }}
- />
-
- ) : null}
- {tokens.length === 1 && !enableTokenIds.includes(tokens[0].tokenId) ? (
-
- {t.nft_not_belong_to_you()}
-
- ) : null}
+
+ {(() => {
+ if (tokens.length) {
+ return (
+
{
+ setErc721TokenId(id)
+ setErc721Address(address)
+ }}
+ />
+ )
+ }
+ if (loading || guessLoading) {
+ return (
+
+
+ {t.tip_loading()}
+
+ )
+ }
+ return (
+
+ {t.tip_empty_nft()}
+
+ {t.tip_add_collectibles()}
+
+
+ )
+ })()}
+
)
}
diff --git a/packages/mask/src/plugins/NextID/components/Tip/TipButton.tsx b/packages/mask/src/plugins/NextID/components/Tip/TipButton.tsx
index aaaad373542e..fa107279382e 100644
--- a/packages/mask/src/plugins/NextID/components/Tip/TipButton.tsx
+++ b/packages/mask/src/plugins/NextID/components/Tip/TipButton.tsx
@@ -1,13 +1,14 @@
import { TipCoin } from '@masknet/icons'
import { usePostInfoDetails } from '@masknet/plugin-infra'
-import { NextIDPlatform, ProfileIdentifier } from '@masknet/shared-base'
+import { EMPTY_LIST, NextIDPlatform, ProfileIdentifier } from '@masknet/shared-base'
import { makeStyles, ShadowRootTooltip } from '@masknet/theme'
-import { queryExistedBindingByPersona, queryIsBound } from '@masknet/web3-providers'
-import { EMPTY_LIST } from '@masknet/web3-shared-evm'
+import { NextIDProof } from '@masknet/web3-providers'
+import type { TooltipProps } from '@mui/material'
import classnames from 'classnames'
import { uniq } from 'lodash-unified'
-import { FC, HTMLProps, MouseEventHandler, useCallback, useMemo } from 'react'
+import { FC, HTMLProps, MouseEventHandler, useCallback, useEffect, useMemo } from 'react'
import { useAsync, useAsyncFn, useAsyncRetry } from 'react-use'
+import { MaskMessages } from '../../../../../shared'
import Services from '../../../../extension/service'
import { activatedSocialNetworkUI } from '../../../../social-network'
import { useI18N } from '../../locales'
@@ -16,6 +17,7 @@ import { PluginNextIdMessages } from '../../messages'
interface Props extends HTMLProps {
addresses?: string[]
receiver?: ProfileIdentifier
+ tooltipProps?: Partial
}
const useStyles = makeStyles()({
@@ -26,20 +28,41 @@ const useStyles = makeStyles()({
alignItems: 'center',
fontFamily: '-apple-system, system-ui, sans-serif',
},
- postTipButton: {
- display: 'flex',
+ buttonWrapper: {
// temporarily hard code
height: 46,
+ display: 'flex',
alignItems: 'center',
color: '#8899a6',
},
+ postTipButton: {
+ cursor: 'pointer',
+ width: 34,
+ height: 34,
+ borderRadius: '100%',
+ '&:hover': {
+ backgroundColor: 'rgba(20,155,240,0.1)',
+ },
+ },
+ tooltip: {
+ backgroundColor: 'rgb(102,102,102)',
+ color: 'white',
+ marginTop: '0 !important',
+ },
disabled: {
opacity: 0.4,
cursor: 'default',
},
})
-export const TipButton: FC = ({ className, receiver, addresses = EMPTY_LIST, children, ...rest }) => {
+export const TipButton: FC = ({
+ className,
+ receiver,
+ addresses = EMPTY_LIST,
+ children,
+ tooltipProps,
+ ...rest
+}) => {
const { classes } = useStyles()
const t = useI18N()
@@ -49,9 +72,13 @@ export const TipButton: FC = ({ className, receiver, addresses = EMPTY_LI
return Services.Identity.queryPersonaByProfile(receiver)
}, [receiver])
- const { value: isAccountVerified, loading: loadingVerifyInfo } = useAsync(() => {
+ const {
+ value: isAccountVerified,
+ loading: loadingVerifyInfo,
+ retry: retryLoadVerifyInfo,
+ } = useAsyncRetry(() => {
if (!receiverPersona?.publicHexKey || !receiver?.userId) return Promise.resolve(false)
- return queryIsBound(receiverPersona.publicHexKey, platform, receiver.userId, true)
+ return NextIDProof.queryIsBound(receiverPersona.publicHexKey, platform, receiver.userId, true)
}, [receiverPersona?.publicHexKey, platform, receiver?.userId])
const [walletsState, queryBindings] = useAsyncFn(async () => {
@@ -60,7 +87,7 @@ export const TipButton: FC = ({ className, receiver, addresses = EMPTY_LI
const persona = await Services.Identity.queryPersonaByProfile(receiver)
if (!persona?.publicHexKey) return EMPTY_LIST
- const bindings = await queryExistedBindingByPersona(persona.publicHexKey, true)
+ const bindings = await NextIDProof.queryExistedBindingByPersona(persona.publicHexKey, true)
if (!bindings) return EMPTY_LIST
const wallets = bindings.proofs.filter((p) => p.platform === NextIDPlatform.Ethereum).map((p) => p.identity)
@@ -69,6 +96,13 @@ export const TipButton: FC = ({ className, receiver, addresses = EMPTY_LI
useAsync(queryBindings, [queryBindings])
+ useEffect(() => {
+ return MaskMessages.events.ownProofChanged.on(() => {
+ retryLoadVerifyInfo()
+ queryBindings()
+ })
+ }, [])
+
const allAddresses = useMemo(() => {
return uniq([...(walletsState.value || []), ...addresses])
}, [walletsState.value, addresses])
@@ -104,15 +138,24 @@ export const TipButton: FC = ({ className, receiver, addresses = EMPTY_LI
if (disabled)
return (
-
+
{dom}
)
return dom
}
-export const PostTipButton: FC = (props) => {
+export const PostTipButton: FC = ({ className, ...rest }) => {
const identifier = usePostInfoDetails.author()
const { classes } = useStyles()
- return
+ return (
+
+
+
+ )
}
diff --git a/packages/mask/src/plugins/NextID/components/Tip/TipDialog.tsx b/packages/mask/src/plugins/NextID/components/Tip/TipDialog.tsx
index 0f0a364ce07b..e0e4c8c442e0 100644
--- a/packages/mask/src/plugins/NextID/components/Tip/TipDialog.tsx
+++ b/packages/mask/src/plugins/NextID/components/Tip/TipDialog.tsx
@@ -1,21 +1,46 @@
-import { SuccessIcon } from '@masknet/icons'
-import { PluginId, useActivatedPlugin, usePluginIDContext } from '@masknet/plugin-infra'
-import { NFTCardStyledAssetPlayer } from '@masknet/shared'
+import { Drop2Icon, LinkOutIcon, SuccessIcon } from '@masknet/icons'
+import {
+ PluginId,
+ useActivatedPlugin,
+ useCurrentWeb3NetworkPluginID,
+ useNetworkDescriptor,
+ useProviderDescriptor,
+ useReverseAddress,
+ useWeb3State,
+} from '@masknet/plugin-infra'
+import { InjectedDialog, NFTCardStyledAssetPlayer, WalletIcon } from '@masknet/shared'
+import { EMPTY_LIST } from '@masknet/shared-base'
+import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
import { makeStyles } from '@masknet/theme'
-import { EMPTY_LIST, TransactionStateType, useChainId, useERC721TokenDetailed } from '@masknet/web3-shared-evm'
-import { DialogContent, Typography } from '@mui/material'
+import {
+ ERC721TokenDetailed,
+ TransactionStateType,
+ useAccount,
+ useChainId,
+ useERC721TokenDetailed,
+} from '@masknet/web3-shared-evm'
+import { DialogContent, Link, Typography } from '@mui/material'
import { useCallback, useEffect, useMemo } from 'react'
import { useBoolean } from 'react-use'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
+import { hasNativeAPI, nativeAPI } from '../../../../../shared/native-rpc'
import { NetworkTab } from '../../../../components/shared/NetworkTab'
import { activatedSocialNetworkUI } from '../../../../social-network'
+import { WalletMessages } from '../../../Wallet/messages'
import { TargetChainIdContext, useTip } from '../../contexts'
import { useI18N } from '../../locales'
import { TipType } from '../../types'
import { ConfirmModal } from '../ConfirmModal'
+import { AddDialog } from './AddDialog'
import { TipForm } from './TipForm'
const useStyles = makeStyles()((theme) => ({
+ dialog: {
+ width: 600,
+ backgroundImage: 'none',
+ },
+ dialogTitle: {
+ height: 60,
+ },
content: {
display: 'flex',
flexDirection: 'column',
@@ -68,6 +93,53 @@ const useStyles = makeStyles()((theme) => ({
width: 64,
height: 64,
},
+ walletChip: {
+ marginLeft: 'auto',
+ height: 40,
+ boxSizing: 'border-box',
+ display: 'flex',
+ alignItems: 'center',
+ backgroundColor: theme.palette.background.default,
+ padding: theme.spacing(0.5, 1),
+ borderRadius: 99,
+ },
+ wallet: {
+ marginLeft: theme.spacing(1),
+ },
+ walletTitle: {
+ marginLeft: theme.spacing(1),
+ lineHeight: '18px',
+ height: 18,
+ fontSize: 14,
+ fontWeight: 'bold',
+ },
+ walletAddress: {
+ height: 12,
+ display: 'flex',
+ alignItems: 'center',
+ fontSize: 10,
+ color: theme.palette.text.secondary,
+ },
+ changeWalletButton: {
+ marginLeft: theme.spacing(0.5),
+ display: 'flex',
+ alignItems: 'center',
+ cursor: 'pointer',
+ },
+ link: {
+ cursor: 'pointer',
+ lineHeight: '10px',
+ marginTop: 2,
+ '&:hover': {
+ textDecoration: 'none',
+ },
+ },
+ linkIcon: {
+ fill: 'none',
+ width: 12,
+ height: 12,
+ marginLeft: theme.spacing(0.5),
+ },
}))
interface TipDialogProps {
@@ -76,31 +148,47 @@ interface TipDialogProps {
}
export function TipDialog({ open = false, onClose }: TipDialogProps) {
- const pluginID = usePluginIDContext()
+ const pluginID = useCurrentWeb3NetworkPluginID()
const tipDefinition = useActivatedPlugin(PluginId.NextID, 'any')
const chainIdList = tipDefinition?.enableRequirement.web3?.[pluginID]?.supportedChainIds ?? EMPTY_LIST
const t = useI18N()
const { classes } = useStyles()
+ const [addTokenDialogIsOpen, openAddTokenDialog] = useBoolean(false)
const [confirmModalIsOpen, openConfirmModal] = useBoolean(false)
const { targetChainId, setTargetChainId } = TargetChainIdContext.useContainer()
- const { tipType, amount, token, recipientSnsId, recipient, sendState, erc721Contract, erc721TokenId } = useTip()
+ const {
+ tipType,
+ amount,
+ token,
+ recipientSnsId,
+ recipient,
+ sendState,
+ erc721Contract,
+ erc721TokenId,
+ setErc721Address,
+ setErc721TokenId,
+ reset,
+ } = useTip()
const isTokenTip = tipType === TipType.Token
- const shareLink = useMemo(() => {
+ const shareText = useMemo(() => {
+ const promote = t.tip_mask_promote()
const message = isTokenTip
? t.tip_token_share_post({
amount,
symbol: token?.symbol || 'token',
recipientSnsId,
recipient,
+ promote,
})
: t.tip_nft_share_post({
- name: erc721Contract?.name || '',
+ name: erc721Contract?.name || 'NFT',
recipientSnsId,
recipient,
+ promote,
})
- return activatedSocialNetworkUI.utils.getShareLinkURL?.(message)
+ return message
}, [amount, isTokenTip, erc721Contract?.name, token, recipient, recipientSnsId, t])
const { tokenDetailed: erc721Token } = useERC721TokenDetailed(erc721Contract, erc721TokenId)
@@ -139,14 +227,73 @@ export function TipDialog({ open = false, onClose }: TipDialogProps) {
}, [sendState.type])
const handleConfirm = useCallback(() => {
- window.open(shareLink)
+ activatedSocialNetworkUI.utils.share?.(shareText)
openConfirmModal(false)
onClose?.()
- }, [shareLink, onClose])
+ }, [shareText, onClose])
+ const networkDescriptor = useNetworkDescriptor()
+ const providerDescriptor = useProviderDescriptor()
+
+ const { Utils } = useWeb3State()
+ const account = useAccount()
+ const { value: domain } = useReverseAddress(account)
+ const walletTitle =
+ Utils?.formatDomainName?.(domain) || Utils?.formatAddress?.(account, 4) || providerDescriptor?.name
+
+ // #region change provider
+ const { openDialog: openSelectProviderDialog } = useRemoteControlledDialog(
+ WalletMessages.events.selectProviderDialogUpdated,
+ )
+ // #endregion
+ const openWallet = useCallback(() => {
+ if (hasNativeAPI) return nativeAPI?.api.misc_openCreateWalletView()
+ return openSelectProviderDialog()
+ }, [openSelectProviderDialog, hasNativeAPI])
+
+ const handleAddToken = useCallback((token: ERC721TokenDetailed) => {
+ setErc721Address(token.contractDetailed.address ?? '')
+ setErc721TokenId(token.tokenId)
+ openAddTokenDialog(false)
+ }, [])
+
+ const walletChip = account ? (
+
+
+
+
+ {walletTitle}
+
+
+
+ {Utils?.formatAddress?.(account, 4)}
+
+
+
+
+
+
+
+
+
+ ) : null
return (
<>
-
+
-
+ openAddTokenDialog(true)} />
openConfirmModal(false)}
+ onClose={() => {
+ openConfirmModal(false)
+ reset()
+ onClose?.()
+ }}
icon={isTokenTip ? : null}
message={successMessage}
confirmText={t.tip_share()}
onConfirm={handleConfirm}
/>
+ openAddTokenDialog(false)} onAdd={handleAddToken} />
>
)
}
diff --git a/packages/mask/src/plugins/NextID/components/Tip/TipForm.tsx b/packages/mask/src/plugins/NextID/components/Tip/TipForm.tsx
index 208680a59c5a..d0f947d54542 100644
--- a/packages/mask/src/plugins/NextID/components/Tip/TipForm.tsx
+++ b/packages/mask/src/plugins/NextID/components/Tip/TipForm.tsx
@@ -1,17 +1,29 @@
import { useWeb3State } from '@masknet/plugin-infra'
-import { SelectTokenDialogEvent, WalletMessages } from '@masknet/plugin-wallet'
+import { WalletMessages } from '@masknet/plugin-wallet'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
import { makeStyles } from '@masknet/theme'
-import { EthereumTokenType, useAccount, useFungibleTokenBalance } from '@masknet/web3-shared-evm'
-import { Box, BoxProps, FormControl, MenuItem, Select, Typography } from '@mui/material'
+import { ChainId, useAccount, useChainId } from '@masknet/web3-shared-evm'
+import {
+ Box,
+ BoxProps,
+ Button,
+ FormControl,
+ FormControlLabel,
+ MenuItem,
+ Radio,
+ RadioGroup,
+ Select,
+ Typography,
+} from '@mui/material'
import classnames from 'classnames'
-import { FC, memo, useCallback, useRef, useState } from 'react'
-import { v4 as uuid } from 'uuid'
+import { FC, memo, useRef, useState } from 'react'
import ActionButton from '../../../../extension/options-page/DashboardComponents/ActionButton'
import { EthereumChainBoundary } from '../../../../web3/UI/EthereumChainBoundary'
-import { TokenAmountPanel } from '../../../../web3/UI/TokenAmountPanel'
import { TargetChainIdContext, useTip, useTipValidate } from '../../contexts'
import { useI18N } from '../../locales'
+import { TipType } from '../../types'
+import { NFTSection } from './NFTSection'
+import { TokenSection } from './TokenSection'
const useStyles = makeStyles()((theme) => {
return {
@@ -25,6 +37,20 @@ const useStyles = makeStyles()((theme) => {
flexGrow: 1,
overflow: 'auto',
},
+ receiverRow: {
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ to: {
+ fontSize: 19,
+ fontWeight: 500,
+ },
+ address: {
+ height: 48,
+ flexGrow: 1,
+ marginLeft: theme.spacing(1),
+ },
actionButton: {
marginTop: theme.spacing(1.5),
fontSize: 16,
@@ -47,76 +73,60 @@ const useStyles = makeStyles()((theme) => {
borderRadius: 24,
height: 'auto',
},
+ controls: {
+ marginTop: theme.spacing(1),
+ display: 'flex',
+ flexDirection: 'row',
+ },
+ addButton: {
+ marginLeft: 'auto',
+ },
tokenField: {
marginTop: theme.spacing(2),
},
}
})
-interface Props extends BoxProps {}
+interface Props extends BoxProps {
+ onAddToken?(): void
+}
-export const TipForm: FC = memo(({ className, ...rest }) => {
+export const TipForm: FC = memo(({ className, onAddToken, ...rest }) => {
const t = useI18N()
+ const currentChainId = useChainId()
const { targetChainId: chainId } = TargetChainIdContext.useContainer()
const { classes } = useStyles()
const {
recipient,
recipients: recipientAddresses,
setRecipient,
- token,
- setToken,
- amount,
- setAmount,
isSending,
sendTip,
+ tipType,
+ setTipType,
} = useTip()
const [isValid, validateMessage] = useTipValidate()
const { Utils } = useWeb3State()
const selectRef = useRef(null)
- const [id] = useState(uuid)
const account = useAccount()
const { openDialog: openSelectProviderDialog } = useRemoteControlledDialog(
WalletMessages.events.selectProviderDialogUpdated,
)
- const { setDialog: setSelectTokenDialog } = useRemoteControlledDialog(
- WalletMessages.events.selectTokenDialogUpdated,
- useCallback(
- (ev: SelectTokenDialogEvent) => {
- if (ev.open || !ev.token || ev.uuid !== id) return
- setToken(ev.token)
- },
- [id],
- ),
- )
- const onSelectTokenChipClick = useCallback(() => {
- setSelectTokenDialog({
- chainId,
- open: true,
- uuid: id,
- disableNativeToken: false,
- FungibleTokenListProps: {
- selectedTokens: token ? [token.address] : [],
- },
- })
- }, [id, token?.address, chainId])
-
- // balance
- const { value: tokenBalance = '0', loading: loadingTokenBalance } = useFungibleTokenBalance(
- token?.type || EthereumTokenType.Native,
- token?.address || '',
- chainId,
- )
- // #endregion
+ const [empty, setEmpty] = useState(false)
const buttonLabel = isSending ? t.sending_tip() : isValid || !validateMessage ? t.send_tip() : validateMessage
+ const enabledNft =
+ !isSending &&
+ chainId === currentChainId &&
+ [ChainId.Mainnet, ChainId.BSC, ChainId.Matic].includes(currentChainId)
return (
- {t.tip_to()}
-
-
+
+ {t.tip_to()}
= memo(({ className, ...rest }) => {
))}
-
-
+
+ setTipType(e.target.value as TipType)}>
+ }
+ label={t.tip_type_token()}
+ />
+ }
+ label={t.tip_type_nft()}
+ />
+
+ {tipType === TipType.NFT && !empty ? (
+
+ {t.tip_add_collectibles()}
+
+ ) : null}
+ {tipType === TipType.Token ? (
+
+
+
+ ) : (
+
+ )}
{account ? (
{
+ const { token, setToken, amount, setAmount, isSending } = useTip()
+ const { targetChainId: chainId } = TargetChainIdContext.useContainer()
+ // balance
+ const { value: tokenBalance = '0', loading: loadingTokenBalance } = useFungibleTokenBalance(
+ token?.type || EthereumTokenType.Native,
+ token?.address || '',
+ chainId,
+ )
+ const gasConfig = useGasConfig(chainId)
+ const maxAmount = useMemo(() => {
+ if (!isNativeTokenAddress(token?.address)) return tokenBalance
+ const gasPrice = gasConfig.gasPrice ?? '1'
+ const gasFee = new BigNumber(gasPrice).times(GAS_LIMIT)
+ return new BigNumber(tokenBalance).minus(gasFee).toFixed()
+ }, [token?.address, tokenBalance, gasConfig.gasPrice])
+ const pickToken = usePickToken()
+ const onSelectTokenChipClick = useCallback(async () => {
+ const picked = await pickToken({
+ chainId,
+ disableNativeToken: false,
+ selectedTokens: token ? [token.address] : [],
+ })
+ if (picked) {
+ setToken(picked)
+ }
+ }, [pickToken, token?.address, chainId])
+ // #endregion
+ return (
+
+ )
+}
diff --git a/packages/mask/src/plugins/NextID/components/UnbindDialog.tsx b/packages/mask/src/plugins/NextID/components/UnbindDialog.tsx
index 16f8b9a498d9..cc0b95ba29b0 100644
--- a/packages/mask/src/plugins/NextID/components/UnbindDialog.tsx
+++ b/packages/mask/src/plugins/NextID/components/UnbindDialog.tsx
@@ -12,7 +12,8 @@ import { useBindPayload } from '../hooks/useBindPayload'
import { delay } from '@dimensiondev/kit'
import { UnbindPanelUI } from './UnbindPanelUI'
import { UnbindConfirm } from './UnbindConfirm'
-import { bindProof } from '@masknet/web3-providers'
+import { NextIDProof } from '@masknet/web3-providers'
+import { MaskMessages } from '../../../../shared'
interface VerifyWalletDialogProps {
unbindAddress: string
@@ -40,7 +41,7 @@ export const UnbindDialog = memo(({ unbindAddress, onCl
if (!personaSignState.value && !walletSignState.value) return
if (!message || !persona.publicHexKey) return
try {
- await bindProof(
+ await NextIDProof.bindProof(
message.uuid,
persona.publicHexKey,
NextIDAction.Delete,
@@ -56,6 +57,9 @@ export const UnbindDialog = memo(({ unbindAddress, onCl
variant: 'success',
message: t.notify_wallet_sign_request_success(),
})
+
+ MaskMessages.events.ownProofChanged.sendToAll()
+
await delay(2000)
onUnBound()
onClose()
diff --git a/packages/mask/src/plugins/NextID/components/UnbindPanelUI.tsx b/packages/mask/src/plugins/NextID/components/UnbindPanelUI.tsx
index 7743ada26936..ab18c807d452 100644
--- a/packages/mask/src/plugins/NextID/components/UnbindPanelUI.tsx
+++ b/packages/mask/src/plugins/NextID/components/UnbindPanelUI.tsx
@@ -7,10 +7,10 @@ import DoneIcon from '@mui/icons-material/Done'
import { useI18N } from '../locales'
import { getMaskColor, makeStyles, MaskColorVar } from '@masknet/theme'
import type { Persona } from '../../../database'
-import { formatFingerprint, LoadingAnimation } from '@masknet/shared'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
-import { NetworkPluginID, usePluginIDContext } from '@masknet/plugin-infra'
+import { InjectedDialog, LoadingAnimation } from '@masknet/shared'
+import { NetworkPluginID, useCurrentWeb3NetworkPluginID } from '@masknet/plugin-infra'
import AbstractTab, { AbstractTabProps } from '../../../components/shared/AbstractTab'
+import { formatPersonaFingerprint } from '@masknet/shared-base'
const useStyles = makeStyles()((theme) => ({
persona: {
@@ -95,7 +95,7 @@ export const UnbindPanelUI = memo(
({ onPersonaSign, onWalletSign, currentPersona, signature, isBound, title, onClose, open, isCurrentAccount }) => {
const t = useI18N()
const { classes } = useStyles()
- const pluginId = usePluginIDContext()
+ const pluginId = useCurrentWeb3NetworkPluginID()
const isSupported = SUPPORTED_PLUGINS.includes(pluginId)
const isWalletSigned = !!signature.wallet.value
@@ -116,7 +116,7 @@ export const UnbindPanelUI = memo(
{currentPersona?.nickname}
- {formatFingerprint(currentPersona?.identifier.compressedPoint ?? '', 10)}
+ {formatPersonaFingerprint(currentPersona?.identifier.compressedPoint ?? '', 10)}
diff --git a/packages/mask/src/plugins/NextID/contexts/TargetChainIdContext.ts b/packages/mask/src/plugins/NextID/contexts/TargetChainIdContext.ts
index 17df3f66514f..ad2d904a03a7 100644
--- a/packages/mask/src/plugins/NextID/contexts/TargetChainIdContext.ts
+++ b/packages/mask/src/plugins/NextID/contexts/TargetChainIdContext.ts
@@ -1,11 +1,15 @@
import { ChainId, useChainId } from '@masknet/web3-shared-evm'
-import { useState } from 'react'
+import { useEffect, useState } from 'react'
import { createContainer } from 'unstated-next'
function useTargetChainId() {
const chainId = useChainId()
const [targetChainId, setTargetChainId] = useState(chainId)
+ useEffect(() => {
+ setTargetChainId(chainId)
+ }, [chainId])
+
return {
targetChainId,
setTargetChainId,
diff --git a/packages/mask/src/plugins/NextID/contexts/Tip/TipContext.ts b/packages/mask/src/plugins/NextID/contexts/Tip/TipContext.ts
index 2082cac59765..8fa4be5af260 100644
--- a/packages/mask/src/plugins/NextID/contexts/Tip/TipContext.ts
+++ b/packages/mask/src/plugins/NextID/contexts/Tip/TipContext.ts
@@ -1,3 +1,4 @@
+import type { Web3Plugin } from '@masknet/plugin-infra'
import {
ERC721ContractDetailed,
FungibleTokenDetailed,
@@ -22,10 +23,13 @@ export interface ContextOptions {
erc721TokenId: string | null
setErc721TokenId: Dispatch>
erc721Contract: ERC721ContractDetailed | null
- setErc721Contract: Dispatch>
+ erc721Address: string
+ setErc721Address: Dispatch>
sendTip: () => Promise
isSending: boolean
sendState: TransactionState
+ storedTokens: Web3Plugin.NonFungibleToken[]
+ reset: () => void
}
export const TipContext = createContext({
@@ -42,8 +46,11 @@ export const TipContext = createContext({
erc721TokenId: null,
setErc721TokenId: noop,
erc721Contract: null,
- setErc721Contract: noop,
+ erc721Address: '',
+ setErc721Address: noop,
sendTip: noop as () => Promise,
isSending: false,
sendState: { type: TransactionStateType.UNKNOWN },
+ storedTokens: [],
+ reset: noop,
})
diff --git a/packages/mask/src/plugins/NextID/contexts/Tip/TipTaskProvider.tsx b/packages/mask/src/plugins/NextID/contexts/Tip/TipTaskProvider.tsx
index de39fd31f565..7b8722fa717e 100644
--- a/packages/mask/src/plugins/NextID/contexts/Tip/TipTaskProvider.tsx
+++ b/packages/mask/src/plugins/NextID/contexts/Tip/TipTaskProvider.tsx
@@ -1,5 +1,12 @@
-import { TransactionStateType, useNativeTokenDetailed } from '@masknet/web3-shared-evm'
+import {
+ TransactionStateType,
+ useChainId,
+ useERC721ContractDetailed,
+ useNativeTokenDetailed,
+} from '@masknet/web3-shared-evm'
import { FC, useContext, useEffect, useMemo, useState } from 'react'
+import { useSubscription } from 'use-subscription'
+import { getStorage } from '../../storage'
import { TipTask, TipType } from '../../types'
import { TargetChainIdContext } from '../TargetChainIdContext'
import { ContextOptions, TipContext } from './TipContext'
@@ -15,10 +22,14 @@ export const TipTaskProvider: FC = ({ children, task }) => {
const [tipType, setTipType] = useState(TipType.Token)
const [amount, setAmount] = useState('')
const { targetChainId } = TargetChainIdContext.useContainer()
- const [erc721Contract, setErc721Contract] = useState(null)
+ const chainId = useChainId()
+ const [erc721Address, setErc721Address] = useState('')
const { value: nativeTokenDetailed = null } = useNativeTokenDetailed(targetChainId)
const [token, setToken] = useState(nativeTokenDetailed)
const [erc721TokenId, setErc721TokenId] = useState(null)
+ const storedTokens = useSubscription(getStorage().addedTokens.subscription)
+
+ const { value: erc721Contract } = useERC721ContractDetailed(erc721Address)
useEffect(() => {
setTipType(TipType.Token)
@@ -38,7 +49,7 @@ export const TipTaskProvider: FC = ({ children, task }) => {
setToken(nativeTokenDetailed)
}, [nativeTokenDetailed])
const tokenTipTuple = useTokenTip(recipient, token, amount)
- const nftTipTuple = useNftTip(recipient, erc721TokenId, erc721Contract)
+ const nftTipTuple = useNftTip(recipient, erc721TokenId, erc721Address)
const sendTipTuple = tipType === TipType.Token ? tokenTipTuple : nftTipTuple
const sendState = sendTipTuple[0]
@@ -51,6 +62,12 @@ export const TipTaskProvider: FC = ({ children, task }) => {
TransactionStateType.RECEIPT,
].includes(sendState.type)
+ const reset = () => {
+ setAmount('')
+ setErc721TokenId(null)
+ setErc721Address('')
+ }
+
return {
recipient,
recipientSnsId: task.recipientSnsId || '',
@@ -64,13 +81,17 @@ export const TipTaskProvider: FC = ({ children, task }) => {
setAmount,
erc721TokenId,
setErc721TokenId,
- erc721Contract,
- setErc721Contract,
+ erc721Contract: erc721Contract || null,
+ erc721Address,
+ setErc721Address,
sendTip,
isSending,
sendState,
+ storedTokens: storedTokens.filter((t) => t.contract?.chainId === chainId),
+ reset,
}
}, [
+ chainId,
recipient,
task.recipientSnsId,
task.addresses,
@@ -78,9 +99,11 @@ export const TipTaskProvider: FC = ({ children, task }) => {
amount,
erc721TokenId,
erc721Contract,
+ erc721Address,
token,
sendTip,
sendState,
+ storedTokens,
])
return {children}
}
diff --git a/packages/mask/src/plugins/NextID/contexts/Tip/useNftTip.ts b/packages/mask/src/plugins/NextID/contexts/Tip/useNftTip.ts
index 398d2cfbe769..da0a1a620572 100644
--- a/packages/mask/src/plugins/NextID/contexts/Tip/useNftTip.ts
+++ b/packages/mask/src/plugins/NextID/contexts/Tip/useNftTip.ts
@@ -1,21 +1,32 @@
-import { ERC721ContractDetailed, EthereumTokenType, useTokenTransferCallback } from '@masknet/web3-shared-evm'
-import { useCallback } from 'react'
+import {
+ EthereumTokenType,
+ useERC721ContractDetailed,
+ useERC721TokenDetailedCallback,
+ useTokenTransferCallback,
+} from '@masknet/web3-shared-evm'
+import { useCallback, useEffect } from 'react'
+import { WalletRPC } from '../../../Wallet/messages'
import type { TipTuple } from './type'
-export function useNftTip(
- recipient: string,
- tokenId: string | null,
- contract: ERC721ContractDetailed | null,
-): TipTuple {
- const [transferState, transferCallback] = useTokenTransferCallback(
- EthereumTokenType.ERC721,
- contract?.address || '',
- )
+export function useNftTip(recipient: string, tokenId: string | null, contractAddress?: string): TipTuple {
+ const [transferState, transferCallback] = useTokenTransferCallback(EthereumTokenType.ERC721, contractAddress || '')
+ const { value: contractDetailed } = useERC721ContractDetailed(contractAddress)
+ const [, setTokenId, erc721TokenDetailedCallback] = useERC721TokenDetailedCallback(contractDetailed)
+
+ useEffect(() => {
+ if (tokenId) {
+ setTokenId(tokenId)
+ }
+ }, [tokenId])
const sendTip = useCallback(async () => {
- if (!tokenId) return
+ if (!tokenId || !contractAddress) return
await transferCallback(tokenId, recipient)
- }, [tokenId, recipient, transferCallback])
+ const tokenDetailed = await erc721TokenDetailedCallback()
+ if (tokenDetailed) {
+ await WalletRPC.removeToken(tokenDetailed)
+ }
+ }, [tokenId, contractAddress, erc721TokenDetailedCallback, recipient, transferCallback])
return [transferState, sendTip]
}
diff --git a/packages/mask/src/plugins/NextID/contexts/Tip/useTipValidate.ts b/packages/mask/src/plugins/NextID/contexts/Tip/useTipValidate.ts
index 741010377f9c..2eca80b4dc8d 100644
--- a/packages/mask/src/plugins/NextID/contexts/Tip/useTipValidate.ts
+++ b/packages/mask/src/plugins/NextID/contexts/Tip/useTipValidate.ts
@@ -8,7 +8,7 @@ import { TipContext } from './TipContext'
type ValidationTuple = [isValid: boolean, message?: string]
export function useTipValidate(): ValidationTuple {
- const { tipType, amount, token, erc721TokenId, erc721Contract } = useContext(TipContext)
+ const { tipType, amount, token, erc721TokenId, erc721Address } = useContext(TipContext)
const { value: balance = '0' } = useFungibleTokenBalance(token?.type || EthereumTokenType.Native, token?.address)
const t = useI18N()
@@ -18,10 +18,10 @@ export function useTipValidate(): ValidationTuple {
if (isGreaterThan(rightShift(amount, token?.decimals), balance))
return [false, t.token_insufficient_balance()]
} else {
- if (!erc721TokenId || !erc721Contract) return [false]
+ if (!erc721TokenId || !erc721Address) return [false]
}
return [true]
- }, [tipType, amount, token?.decimals, balance, erc721Contract, erc721TokenId, t])
+ }, [tipType, amount, token?.decimals, balance, erc721TokenId, erc721Address, t])
return result
}
diff --git a/packages/mask/src/plugins/NextID/hooks/useBindPayload.ts b/packages/mask/src/plugins/NextID/hooks/useBindPayload.ts
index b995a4577f79..8ae719362503 100644
--- a/packages/mask/src/plugins/NextID/hooks/useBindPayload.ts
+++ b/packages/mask/src/plugins/NextID/hooks/useBindPayload.ts
@@ -1,11 +1,11 @@
import { useAsyncRetry } from 'react-use'
-import { createPersonaPayload } from '@masknet/web3-providers'
+import { NextIDProof } from '@masknet/web3-providers'
import { NextIDAction, NextIDPlatform } from '@masknet/shared-base'
export const useBindPayload = (action: NextIDAction, address?: string, currentIdentifier?: string) => {
return useAsyncRetry(() => {
if (!address) return Promise.resolve(undefined)
if (!currentIdentifier || !address) return Promise.resolve(undefined)
- return createPersonaPayload(currentIdentifier, action, address, NextIDPlatform.Ethereum)
+ return NextIDProof.createPersonaPayload(currentIdentifier, action, address, NextIDPlatform.Ethereum)
}, [currentIdentifier, address])
}
diff --git a/packages/mask/src/plugins/NextID/locales/en-US.json b/packages/mask/src/plugins/NextID/locales/en-US.json
index a271b612e0ed..fda218ebf57a 100644
--- a/packages/mask/src/plugins/NextID/locales/en-US.json
+++ b/packages/mask/src/plugins/NextID/locales/en-US.json
@@ -53,13 +53,22 @@
"send_tip": "Send",
"sending_tip": "Sending...",
"nft_not_belong_to_you": "The collectible doesn't exist or belong to you.",
- "send_tip_successfully": "Sent tip successfully",
- "send_specific_tip_successfully": "Sent {{amount}} {{name}} tip successfully",
+ "send_tip_successfully": "Sent tip successfully.",
+ "send_specific_tip_successfully": "Sent {{amount}} {{name}} tip successfully.",
"tip_share": "Share",
"tip_connect_wallet_message": "Please connect to a wallet.",
"tip_connect_wallet": "Connect Wallet",
"search": "Search",
"tip_contracts": "Contracts",
- "tip_token_share_post": "I just tipped {{amount}} {{symbol}} to @{{recipientSnsId}}'s wallet address {{recipient}}\n\nInstall https://mask.io/download-links to send my first tip.",
- "tip_nft_share_post": "I just tipped a {{name}} to @{{recipientSnsId}}'s wallet address {{recipient}}\n\nInstall https://mask.io/download-links to send my first tip."
+ "tip_mask_promote": "Install https://mask.io/download-links to send your first tip.",
+ "tip_token_share_post": "I just tipped {{amount}} {{symbol}} to @{{recipientSnsId}}'s wallet address {{recipient}}\n\n{{promote}}",
+ "tip_nft_share_post": "I just tipped a {{name}} to @{{recipientSnsId}}'s wallet address {{recipient}}\n\n{{promote}}",
+ "tip_add": "Add",
+ "tip_adding": "Adding",
+ "tip_add_collectibles": "Add Collectibles",
+ "tip_add_collectibles_contract_address": "Input contract address",
+ "tip_add_collectibles_token_id": "Token ID",
+ "tip_add_collectibles_error": "The contract address is incorrect or the collectible does not belong to you.",
+ "tip_loading": "Loading",
+ "tip_empty_nft": "No any collectible is available to preview. Please add your collectible here."
}
diff --git a/packages/mask/src/plugins/NextID/storage/index.ts b/packages/mask/src/plugins/NextID/storage/index.ts
new file mode 100644
index 000000000000..6c9312b13e59
--- /dev/null
+++ b/packages/mask/src/plugins/NextID/storage/index.ts
@@ -0,0 +1,38 @@
+import type { Web3Plugin } from '@masknet/plugin-infra'
+import type { ScopedStorage } from '@masknet/shared-base'
+import { isSameAddress } from '@masknet/web3-shared-evm'
+import { remove } from 'lodash-unified'
+
+interface StorageValue {
+ addedTokens: Web3Plugin.NonFungibleToken[]
+}
+
+export const storageDefaultValue = {
+ publicKey: null as null | string,
+ addedTokens: [],
+}
+
+let storage: ScopedStorage = null!
+
+export function setupStorage(_: typeof storage) {
+ storage = _
+}
+
+export function getStorage() {
+ return storage.storage
+}
+
+export function getTokens() {
+ return storage.storage.addedTokens.value
+}
+
+export function storeToken(token: Web3Plugin.NonFungibleToken) {
+ const tokens = [token, ...getTokens()]
+ storage.storage.addedTokens.setValue(tokens)
+}
+
+export function deleteToken(address: string, tokenId: string) {
+ const tokens = getTokens()
+ remove(tokens, (t) => t.tokenId === tokenId && isSameAddress(t.contract?.address, address))
+ storage.storage.addedTokens.setValue(tokens)
+}
diff --git a/packages/mask/src/plugins/NextID/types/tip.ts b/packages/mask/src/plugins/NextID/types/tip.ts
index 2e6cbde0e5cd..8fefaf25e33a 100644
--- a/packages/mask/src/plugins/NextID/types/tip.ts
+++ b/packages/mask/src/plugins/NextID/types/tip.ts
@@ -8,3 +8,5 @@ export interface TipTask {
recipientSnsId?: string
addresses: string[]
}
+
+export type TipNFTKeyPair = [address: string, tokenId: string]
diff --git a/packages/mask/src/plugins/Pets/SNSAdaptor/PetDialog.tsx b/packages/mask/src/plugins/Pets/SNSAdaptor/PetDialog.tsx
index b9efb0a31221..43b6a5c96385 100644
--- a/packages/mask/src/plugins/Pets/SNSAdaptor/PetDialog.tsx
+++ b/packages/mask/src/plugins/Pets/SNSAdaptor/PetDialog.tsx
@@ -2,9 +2,9 @@ import { useState } from 'react'
import { useAsync, useTimeout } from 'react-use'
import type { Constant } from '@masknet/web3-shared-evm/constants/utils'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog } from '@masknet/shared'
import { DialogContent } from '@mui/material'
import { PluginPetMessages, PluginPetRPC } from '../messages'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
import { useI18N } from '../../../utils'
import { PetShareDialog } from './PetShareDialog'
import { PetSetDialog } from './PetSetDialog'
diff --git a/packages/mask/src/plugins/Pets/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Pets/SNSAdaptor/index.tsx
index 9c3e1ca4d06e..b8e87d5b6bf3 100644
--- a/packages/mask/src/plugins/Pets/SNSAdaptor/index.tsx
+++ b/packages/mask/src/plugins/Pets/SNSAdaptor/index.tsx
@@ -2,6 +2,9 @@ import type { Plugin } from '@masknet/plugin-infra'
import { base } from '../base'
import AnimatePic from './Animate'
import { PetDialog } from './PetDialog'
+import { PluginPetMessages } from '../messages'
+import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { ApplicationEntry } from '@masknet/shared'
const sns: Plugin.SNSAdaptor.Definition = {
...base,
@@ -14,6 +17,23 @@ const sns: Plugin.SNSAdaptor.Definition = {
>
)
},
+ ApplicationEntries: [
+ {
+ RenderEntryComponent({ disabled }) {
+ const { openDialog } = useRemoteControlledDialog(PluginPetMessages.events.essayDialogUpdated)
+
+ return (
+
+ )
+ },
+ defaultSortingPriority: 10,
+ },
+ ],
}
export default sns
diff --git a/packages/mask/src/components/shared/assets/mintTeam.png b/packages/mask/src/plugins/Pets/assets/mintTeam.png
similarity index 100%
rename from packages/mask/src/components/shared/assets/mintTeam.png
rename to packages/mask/src/plugins/Pets/assets/mintTeam.png
diff --git a/packages/mask/src/plugins/Pets/base.tsx b/packages/mask/src/plugins/Pets/base.tsx
index 119d8bf64690..903598b14901 100644
--- a/packages/mask/src/plugins/Pets/base.tsx
+++ b/packages/mask/src/plugins/Pets/base.tsx
@@ -1,4 +1,5 @@
-import { CurrentSNSNetwork, Plugin } from '@masknet/plugin-infra'
+import { CurrentSNSNetwork, Plugin, NetworkPluginID } from '@masknet/plugin-infra'
+import { ChainId } from '@masknet/web3-shared-evm'
import { PetsPluginID } from './constants'
import { PETSIcon } from '../../resources/PETSIcon'
@@ -13,6 +14,13 @@ export const base: Plugin.Shared.Definition = {
enableRequirement: {
architecture: { app: false, web: true },
networks: { type: 'opt-in', networks: { [CurrentSNSNetwork.Twitter]: true } },
+ web3: {
+ [NetworkPluginID.PLUGIN_EVM]: {
+ supportedChainIds: [ChainId.Mainnet],
+ },
+ [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] },
+ [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] },
+ },
target: 'stable',
},
}
diff --git a/packages/mask/src/plugins/Polls/SNSAdaptor/PollsDialog.tsx b/packages/mask/src/plugins/Polls/SNSAdaptor/PollsDialog.tsx
index 66df37c2f593..ce0ff662c48f 100644
--- a/packages/mask/src/plugins/Polls/SNSAdaptor/PollsDialog.tsx
+++ b/packages/mask/src/plugins/Polls/SNSAdaptor/PollsDialog.tsx
@@ -15,6 +15,7 @@ import {
import { makeStyles, useStylesExtends, usePortalShadowRoot } from '@masknet/theme'
import AddIcon from '@mui/icons-material/Add'
import addDate from 'date-fns/add'
+import { InjectedDialog } from '@masknet/shared'
import { useI18N } from '../../../utils'
import AbstractTab, { AbstractTabProps } from '../../../components/shared/AbstractTab'
import { useCurrentIdentity } from '../../../components/DataSource/useActivatedUI'
@@ -22,7 +23,6 @@ import type { PollGunDB } from '../Services'
import { PollCardUI } from './Polls'
import type { PollMetaData } from '../types'
import { PLUGIN_META_KEY } from '../constants'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
import { PluginPollRPC } from '../messages'
import { useCompositionContext } from '@masknet/plugin-infra'
diff --git a/packages/mask/src/plugins/PoolTogether/UI/DepositDialog.tsx b/packages/mask/src/plugins/PoolTogether/UI/DepositDialog.tsx
index bce5d39ccd42..552221d0c1e5 100644
--- a/packages/mask/src/plugins/PoolTogether/UI/DepositDialog.tsx
+++ b/packages/mask/src/plugins/PoolTogether/UI/DepositDialog.tsx
@@ -1,21 +1,21 @@
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { usePickToken, InjectedDialog } from '@masknet/shared'
+import { keyframes, makeStyles } from '@masknet/theme'
+import { isZero, rightShift } from '@masknet/web3-shared-base'
import {
EthereumTokenType,
formatBalance,
FungibleTokenDetailed,
TransactionStateType,
useAccount,
- ZERO_ADDRESS,
useFungibleTokenBalance,
+ ZERO_ADDRESS,
} from '@masknet/web3-shared-evm'
-import { isZero, rightShift } from '@masknet/web3-shared-base'
import { DialogContent, Grid, Typography } from '@mui/material'
-import { keyframes, makeStyles } from '@masknet/theme'
import { useCallback, useEffect, useMemo, useState } from 'react'
-import { v4 as uuid } from 'uuid'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton'
import { activatedSocialNetworkUI } from '../../../social-network'
+import { isFacebook } from '../../../social-network-adaptor/facebook.com/base'
import { isTwitter } from '../../../social-network-adaptor/twitter.com/base'
import { useI18N } from '../../../utils/i18n-next-ui'
import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary'
@@ -23,12 +23,11 @@ import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWallet
import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel'
import { PluginTraderMessages } from '../../Trader/messages'
import type { Coin } from '../../Trader/types'
-import { SelectTokenDialogEvent, WalletMessages } from '../../Wallet/messages'
+import { WalletMessages } from '../../Wallet/messages'
import { useDepositCallback } from '../hooks/useDepositCallback'
import { PluginPoolTogetherMessages } from '../messages'
import type { Pool } from '../types'
import { calculateOdds, getPrizePeriod } from '../utils'
-import { isFacebook } from '../../../social-network-adaptor/facebook.com/base'
const rainbow_animation = keyframes`
0% {
@@ -82,7 +81,6 @@ const useStyles = makeStyles()((theme) => ({
export function DepositDialog() {
const { t } = useI18N()
const { classes } = useStyles()
- const [id] = useState(uuid())
const [pool, setPool] = useState()
const [token, setToken] = useState()
const [odds, setOdds] = useState()
@@ -102,28 +100,16 @@ export function DepositDialog() {
// #endregion
// #region select token
- const { setDialog: setSelectTokenDialogOpen } = useRemoteControlledDialog(
- WalletMessages.events.selectTokenDialogUpdated,
- useCallback(
- (ev: SelectTokenDialogEvent) => {
- if (ev.open || !ev.token || ev.uuid !== id) return
- setToken(ev.token)
- },
- [id],
- ),
- )
- const onSelectTokenChipClick = useCallback(() => {
+ const pickToken = usePickToken()
+ const onSelectTokenChipClick = useCallback(async () => {
if (!token) return
- setSelectTokenDialogOpen({
- open: true,
- uuid: id,
+ const picked = await pickToken({
disableNativeToken: true,
- FungibleTokenListProps: {
- selectedTokens: [token.address],
- whitelist: [token.address],
- },
+ selectedTokens: [token.address],
+ whitelist: [token.address],
})
- }, [id, token?.address])
+ if (picked) setToken(picked)
+ }, [token, pickToken])
// #endregion
// #region amount
@@ -216,7 +202,7 @@ export function DepositDialog() {
if (depositState.type === TransactionStateType.HASH) setRawAmount('')
resetDepositCallback()
},
- [id, depositState, retryLoadTokenBalance, retryLoadTokenBalance, onClose],
+ [depositState, retryLoadTokenBalance, retryLoadTokenBalance, onClose],
),
)
diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketCreateNew.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketCreateNew.tsx
index 2c1107f5b84f..22a399ef1503 100644
--- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketCreateNew.tsx
+++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketCreateNew.tsx
@@ -1,10 +1,10 @@
import { makeStyles } from '@masknet/theme'
+import { MINDS_ID } from '@masknet/shared'
import { useChainId, ChainId } from '@masknet/web3-shared-evm'
import { RedPacketFormProps, RedPacketERC20Form } from './RedPacketERC20Form'
import { RedPacketERC721Form } from './RedPacketERC721Form'
import AbstractTab, { AbstractTabProps } from '../../../components/shared/AbstractTab'
import { useI18N } from '../../../utils'
-import { MINDS_ID } from '../../../social-network-adaptor/minds.com/base'
import { activatedSocialNetworkUI } from '../../../social-network'
import { IconURLs } from './IconURL'
diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketDialog.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketDialog.tsx
index 919c9432e430..953069f0ff3c 100644
--- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketDialog.tsx
+++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketDialog.tsx
@@ -2,6 +2,7 @@ import { useState, useCallback, useRef, useMemo, useEffect } from 'react'
import { DialogContent } from '@mui/material'
import { usePortalShadowRoot, makeStyles } from '@masknet/theme'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog } from '@masknet/shared'
import { useI18N } from '../../../utils'
import AbstractTab, { AbstractTabProps } from '../../../components/shared/AbstractTab'
import { RedPacketJSONPayload, DialogTabs, RedPacketRecord, RpTypeTabs } from '../types'
@@ -9,7 +10,6 @@ import { RedPacketRPC } from '../messages'
import { RedPacketMetaKey } from '../constants'
import { RedPacketCreateNew } from './RedPacketCreateNew'
import { RedPacketPast } from './RedPacketPast'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
import Services from '../../../extension/service'
import Web3Utils from 'web3-utils'
import {
diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketERC20Form.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketERC20Form.tsx
index 2bf6f54f6503..6e0673921c71 100644
--- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketERC20Form.tsx
+++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketERC20Form.tsx
@@ -1,28 +1,26 @@
-import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { makeStyles, useStylesExtends } from '@masknet/theme'
+import { isGreaterThan, isZero, multipliedBy, rightShift } from '@masknet/web3-shared-base'
import {
EthereumTokenType,
formatBalance,
FungibleTokenDetailed,
useAccount,
+ useChainId,
+ useFungibleTokenBalance,
useNativeTokenDetailed,
useRedPacketConstants,
- useFungibleTokenBalance,
- useChainId,
} from '@masknet/web3-shared-evm'
-import { isGreaterThan, isZero, multipliedBy, rightShift } from '@masknet/web3-shared-base'
-import { omit } from 'lodash-unified'
import { FormControl, InputLabel, MenuItem, MenuProps, Select, TextField } from '@mui/material'
-import { makeStyles, useStylesExtends } from '@masknet/theme'
import BigNumber from 'bignumber.js'
-import { ChangeEvent, useCallback, useMemo, useState, useEffect } from 'react'
-import { v4 as uuid } from 'uuid'
+import { omit } from 'lodash-unified'
+import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'
+import { usePickToken } from '@masknet/shared'
import { useCurrentIdentity } from '../../../components/DataSource/useActivatedUI'
import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton'
import { useI18N } from '../../../utils'
import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary'
import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary'
import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel'
-import { SelectTokenDialogEvent, WalletMessages } from '../../Wallet/messages'
import { RED_PACKET_DEFAULT_SHARES, RED_PACKET_MAX_SHARES, RED_PACKET_MIN_SHARES } from '../constants'
import type { RedPacketSettings } from './hooks/useCreateCallback'
@@ -96,27 +94,14 @@ export function RedPacketERC20Form(props: RedPacketFormProps) {
// #region select token
const { value: nativeTokenDetailed } = useNativeTokenDetailed()
const [token = nativeTokenDetailed, setToken] = useState(origin?.token)
- const [id] = useState(uuid)
- const { setDialog: setSelectTokenDialog } = useRemoteControlledDialog(
- WalletMessages.events.selectTokenDialogUpdated,
- useCallback(
- (ev: SelectTokenDialogEvent) => {
- if (ev.open || !ev.token || ev.uuid !== id) return
- setToken(ev.token)
- },
- [id],
- ),
- )
- const onSelectTokenChipClick = useCallback(() => {
- setSelectTokenDialog({
- open: true,
- uuid: id,
+ const pickToken = usePickToken()
+ const onSelectTokenChipClick = useCallback(async () => {
+ const picked = await pickToken({
disableNativeToken: false,
- FungibleTokenListProps: {
- selectedTokens: token ? [token.address] : [],
- },
+ selectedTokens: token ? [token.address] : [],
})
- }, [id, token?.address])
+ if (picked) setToken(picked)
+ }, [pickToken, token?.address])
// #endregion
// #region packet settings
diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketInHistoryList.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketInHistoryList.tsx
index 73fe3c6ce61e..3a5495fe28ca 100644
--- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketInHistoryList.tsx
+++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketInHistoryList.tsx
@@ -4,6 +4,7 @@ import classNames from 'classnames'
import { Box, ListItem, Typography, Popper, useMediaQuery, Theme } from '@mui/material'
import { makeStyles } from '@masknet/theme'
import { Trans } from 'react-i18next'
+import { omit } from 'lodash-unified'
import { RedPacketJSONPayload, RedPacketStatus, RedPacketJSONPayloadFromChain } from '../types'
import { TokenIcon } from '@masknet/shared'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
@@ -14,6 +15,7 @@ import {
useAccount,
isSameAddress,
EthereumTokenType,
+ FungibleTokenDetailed,
useFungibleTokenDetailed,
useTokenConstants,
} from '@masknet/web3-shared-evm'
@@ -236,7 +238,7 @@ export function RedPacketInHistoryList(props: RedPacketInHistoryListProps) {
const onSendOrRefund = useCallback(async () => {
if (canRefund) await refundCallback()
- if (canSend) onSelect({ ...history, token: historyToken })
+ if (canSend) onSelect(removeUselessSendParams({ ...history, token: historyToken as FungibleTokenDetailed }))
}, [onSelect, refundCallback, canRefund, canSend, history])
// #region password lost tips
@@ -379,3 +381,10 @@ export function RedPacketInHistoryList(props: RedPacketInHistoryListProps) {
)
}
+
+function removeUselessSendParams(payload: RedPacketJSONPayload): RedPacketJSONPayload {
+ return {
+ ...omit(payload, ['block_number', 'claimers']),
+ token: omit(payload.token, ['logoURI']) as FungibleTokenDetailed,
+ }
+}
diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNft.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNft.tsx
index 81fbde4d68bc..996f2814db13 100644
--- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNft.tsx
+++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNft.tsx
@@ -22,6 +22,7 @@ import { activatedSocialNetworkUI } from '../../../social-network'
import { isTwitter } from '../../../social-network-adaptor/twitter.com/base'
import { isFacebook } from '../../../social-network-adaptor/facebook.com/base'
import { NFTCardStyledAssetPlayer } from '@masknet/shared'
+import { openWindow } from '@masknet/shared-base-ui'
const useStyles = makeStyles()((theme) => ({
root: {
@@ -261,11 +262,7 @@ export function RedPacketNft({ payload }: RedPacketNftProps) {
const isClaiming = claimState.type === TransactionStateType.WAIT_FOR_CONFIRMING
const openAddressLinkOnExplorer = useCallback(() => {
- window.open(
- resolveAddressLinkOnExplorer(payload.chainId, payload.contractAddress),
- '_blank',
- 'noopener noreferrer',
- )
+ openWindow(resolveAddressLinkOnExplorer(payload.chainId, payload.contractAddress))
}, [payload])
const [sourceType, setSourceType] = useState('')
diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNftShare.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNftShare.tsx
index 58cec1c5cf91..4afea0220ecd 100644
--- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNftShare.tsx
+++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNftShare.tsx
@@ -1,6 +1,6 @@
import { makeStyles } from '@masknet/theme'
import { Button, DialogActions, DialogContent } from '@mui/material'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import { useI18N } from '../../../utils'
const useStyles = makeStyles()((theme) => ({
diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedpacketNftConfirmDialog.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedpacketNftConfirmDialog.tsx
index 0c7b53582212..cd7890b3b85f 100644
--- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedpacketNftConfirmDialog.tsx
+++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedpacketNftConfirmDialog.tsx
@@ -13,10 +13,9 @@ import {
isNativeTokenAddress,
formatNFT_TokenId,
} from '@masknet/web3-shared-evm'
-import { NFTCardStyledAssetPlayer } from '@masknet/shared'
+import { InjectedDialog, NFTCardStyledAssetPlayer } from '@masknet/shared'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
import classNames from 'classnames'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
import { Button, Grid, Link, Typography, DialogContent, List, ListItem } from '@mui/material'
import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton'
import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary'
diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx
index 21a3ac584938..5a7c8435cfa0 100644
--- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx
+++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx
@@ -1,5 +1,5 @@
import classNames from 'classnames'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog, NFTCardStyledAssetPlayer } from '@masknet/shared'
import {
ERC721TokenDetailed,
ERC721ContractDetailed,
@@ -17,7 +17,6 @@ import { SearchIcon } from '@masknet/icons'
import CheckIcon from '@mui/icons-material/Check'
import { Trans } from 'react-i18next'
import { useUpdate } from 'react-use'
-import { NFTCardStyledAssetPlayer } from '@masknet/shared'
import { findLastIndex } from 'lodash-unified'
import { NFT_RED_PACKET_MAX_SHARES } from '../constants'
diff --git a/packages/mask/src/components/shared/assets/lucky_drop.png b/packages/mask/src/plugins/RedPacket/SNSAdaptor/assets/lucky_drop.png
similarity index 100%
rename from packages/mask/src/components/shared/assets/lucky_drop.png
rename to packages/mask/src/plugins/RedPacket/SNSAdaptor/assets/lucky_drop.png
diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/index.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/index.tsx
index 3b60fb8794f0..e8e633e2390e 100644
--- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/index.tsx
+++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/index.tsx
@@ -20,6 +20,8 @@ import RedPacketDialog from './RedPacketDialog'
import { RedPacketInPost } from './RedPacketInPost'
import { RedPacketNftInPost } from './RedPacketNftInPost'
import { RedPacketIcon, NFTRedPacketIcon } from '@masknet/icons'
+import { CrossIsolationMessages } from '@masknet/shared-base'
+import { ApplicationEntry } from '@masknet/shared'
function Render(props: React.PropsWithChildren<{ name: string }>) {
usePluginWrapper(true, { name: props.name })
@@ -90,6 +92,29 @@ const sns: Plugin.SNSAdaptor.Definition = {
),
},
},
+ ApplicationEntries: [
+ {
+ RenderEntryComponent({ disabled }) {
+ return (
+
+ CrossIsolationMessages.events.requestComposition.sendToLocal({
+ reason: 'timeline',
+ open: true,
+ options: {
+ startupPlugin: base.ID,
+ },
+ })
+ }
+ />
+ )
+ },
+ defaultSortingPriority: 1,
+ },
+ ],
}
interface ERC20RedpacketBadgeProps {
payload: RedPacketJSONPayload
diff --git a/packages/mask/src/plugins/RedPacket/base.ts b/packages/mask/src/plugins/RedPacket/base.ts
index 25fc85ada510..c7d4bfa4c6c5 100644
--- a/packages/mask/src/plugins/RedPacket/base.ts
+++ b/packages/mask/src/plugins/RedPacket/base.ts
@@ -13,7 +13,10 @@ export const base: Plugin.Shared.Definition = {
publisher: { name: { fallback: 'Mask Network' }, link: 'https://mask.io/' },
enableRequirement: {
architecture: { app: true, web: true },
- networks: { type: 'opt-out', networks: {} },
+ networks: {
+ type: 'opt-out',
+ networks: {},
+ },
target: 'stable',
web3: {
[NetworkPluginID.PLUGIN_EVM]: {
@@ -29,6 +32,8 @@ export const base: Plugin.Shared.Definition = {
ChainId.Conflux,
],
},
+ [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] },
+ [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] },
},
},
contribution: {
diff --git a/packages/mask/src/plugins/Savings/SNSAdaptor/SavingsDialog.tsx b/packages/mask/src/plugins/Savings/SNSAdaptor/SavingsDialog.tsx
index dfa2dd5b16be..81c9d8a89829 100644
--- a/packages/mask/src/plugins/Savings/SNSAdaptor/SavingsDialog.tsx
+++ b/packages/mask/src/plugins/Savings/SNSAdaptor/SavingsDialog.tsx
@@ -2,10 +2,9 @@ import { useState, useMemo } from 'react'
import { useAsync } from 'react-use'
import { Typography, DialogContent } from '@mui/material'
import { ChainId, getChainIdFromNetworkType, useChainId } from '@masknet/web3-shared-evm'
-import { isDashboardPage } from '@masknet/shared-base'
+import { isDashboardPage, EMPTY_LIST } from '@masknet/shared-base'
import { useI18N } from '../../../utils'
-import { EMPTY_LIST } from '../../../../utils-pure'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import { WalletStatusBox } from '../../../components/shared/WalletStatusBox'
import { AllProviderTradeContext } from '../../Trader/trader/useAllProviderTradeContext'
import { TargetChainIdContext } from '../../Trader/trader/useTargetChainIdContext'
@@ -58,7 +57,7 @@ export function SavingsDialog({ open, onClose }: SavingsDialogProps) {
{
if (selectedProtocol === null) {
onClose?.()
diff --git a/packages/mask/src/components/shared/assets/savings.png b/packages/mask/src/plugins/Savings/SNSAdaptor/assets/savings.png
similarity index 100%
rename from packages/mask/src/components/shared/assets/savings.png
rename to packages/mask/src/plugins/Savings/SNSAdaptor/assets/savings.png
diff --git a/packages/mask/src/plugins/Savings/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Savings/SNSAdaptor/index.tsx
index cbabb7a159eb..500c25fb60b7 100644
--- a/packages/mask/src/plugins/Savings/SNSAdaptor/index.tsx
+++ b/packages/mask/src/plugins/Savings/SNSAdaptor/index.tsx
@@ -1,9 +1,31 @@
import type { Plugin } from '@masknet/plugin-infra'
+import { ApplicationEntry } from '@masknet/shared'
+import { useState } from 'react'
import { base } from '../base'
+import { SavingsDialog } from './SavingsDialog'
const sns: Plugin.SNSAdaptor.Definition = {
...base,
init(signal) {},
+ ApplicationEntries: [
+ {
+ RenderEntryComponent({ disabled }) {
+ const [open, setOpen] = useState(false)
+ return (
+ <>
+ setOpen(true)}
+ />
+ setOpen(false)} />
+ >
+ )
+ },
+ defaultSortingPriority: 7,
+ },
+ ],
}
export default sns
diff --git a/packages/mask/src/plugins/Savings/base.ts b/packages/mask/src/plugins/Savings/base.ts
index 8dc78be6713d..9133a1f2b61e 100644
--- a/packages/mask/src/plugins/Savings/base.ts
+++ b/packages/mask/src/plugins/Savings/base.ts
@@ -1,4 +1,5 @@
-import type { Plugin } from '@masknet/plugin-infra'
+import { Plugin, NetworkPluginID } from '@masknet/plugin-infra'
+import { ChainId } from '@masknet/web3-shared-evm'
import { SAVINGS_PLUGIN_ID } from './constants'
export const base: Plugin.Shared.Definition = {
@@ -11,7 +12,26 @@ export const base: Plugin.Shared.Definition = {
publisher: { name: { fallback: 'Mask Network' }, link: 'https://mask.io/' },
enableRequirement: {
architecture: { app: true, web: true },
- networks: { type: 'opt-out', networks: {} },
+ networks: {
+ type: 'opt-out',
+ networks: {},
+ },
target: 'stable',
+ web3: {
+ [NetworkPluginID.PLUGIN_EVM]: {
+ supportedChainIds: [
+ ChainId.Mainnet,
+ ChainId.BSC,
+ ChainId.Matic,
+ ChainId.Arbitrum,
+ ChainId.xDai,
+ ChainId.Aurora,
+ ChainId.Avalanche,
+ ChainId.Fantom,
+ ],
+ },
+ [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] },
+ [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] },
+ },
},
}
diff --git a/packages/mask/src/plugins/Snapshot/SNSAdaptor/VoteConfirmDialog.tsx b/packages/mask/src/plugins/Snapshot/SNSAdaptor/VoteConfirmDialog.tsx
index fd09775e3b8f..54ab254ce7a0 100644
--- a/packages/mask/src/plugins/Snapshot/SNSAdaptor/VoteConfirmDialog.tsx
+++ b/packages/mask/src/plugins/Snapshot/SNSAdaptor/VoteConfirmDialog.tsx
@@ -14,7 +14,7 @@ import millify from 'millify'
import OpenInNew from '@mui/icons-material/OpenInNew'
import { resolveBlockLinkOnExplorer, ChainId } from '@masknet/web3-shared-evm'
import { useI18N } from '../../../utils'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import { InfoField } from './InformationCard'
import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary'
diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/index.tsx
index 29c369c34569..170cab6a531c 100644
--- a/packages/mask/src/plugins/Trader/SNSAdaptor/index.tsx
+++ b/packages/mask/src/plugins/Trader/SNSAdaptor/index.tsx
@@ -2,8 +2,11 @@ import type { Plugin } from '@masknet/plugin-infra'
import { base } from '../base'
import { TraderDialog } from './trader/TraderDialog'
import { SearchResultInspector } from './trending/SearchResultInspector'
+import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
import { TagInspector } from './trending/TagInspector'
import { enhanceTag } from './cashTag'
+import { ApplicationEntry } from '@masknet/shared'
+import { PluginTraderMessages } from '../messages'
const sns: Plugin.SNSAdaptor.Definition = {
...base,
@@ -18,6 +21,23 @@ const sns: Plugin.SNSAdaptor.Definition = {
)
},
enhanceTag,
+ ApplicationEntries: [
+ {
+ RenderEntryComponent({ disabled }) {
+ const { openDialog } = useRemoteControlledDialog(PluginTraderMessages.swapDialogUpdated)
+
+ return (
+
+ )
+ },
+ defaultSortingPriority: 8,
+ },
+ ],
}
export default sns
diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/ConfirmDialog.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/ConfirmDialog.tsx
index d96df5edf97c..b66e38e1452c 100644
--- a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/ConfirmDialog.tsx
+++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/ConfirmDialog.tsx
@@ -3,9 +3,9 @@ import { ExternalLink } from 'react-feather'
import BigNumber from 'bignumber.js'
import { Alert, Box, Button, DialogActions, DialogContent, Link, Typography } from '@mui/material'
import { makeStyles, MaskColorVar, useStylesExtends } from '@masknet/theme'
-import { FormattedAddress, FormattedBalance, TokenIcon } from '@masknet/shared'
+import { useValueRef } from '@masknet/shared-base-ui'
+import { InjectedDialog, FormattedAddress, FormattedBalance, TokenIcon } from '@masknet/shared'
import type { TradeComputed } from '../../types'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
import type { FungibleTokenDetailed, Wallet } from '@masknet/web3-shared-evm'
import {
createNativeToken,
@@ -19,7 +19,6 @@ import { useI18N } from '../../../../utils'
import { InfoIcon, RetweetIcon, CramIcon } from '@masknet/icons'
import { isZero, multipliedBy } from '@masknet/web3-shared-base'
import { isDashboardPage } from '@masknet/shared-base'
-import { useValueRef } from '@masknet/shared-base-ui'
import { TargetChainIdContext } from '../../trader/useTargetChainIdContext'
import { currentSlippageSettings } from '../../settings'
import { useNativeTokenPrice } from '../../../Wallet/hooks/useTokenPrice'
diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/SettingsDialog.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/SettingsDialog.tsx
index 919cd78c7776..95d5176f9000 100644
--- a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/SettingsDialog.tsx
+++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/SettingsDialog.tsx
@@ -1,11 +1,11 @@
import { useState, useCallback, useEffect } from 'react'
import { Accordion, AccordionDetails, AccordionSummary, Alert, DialogContent, Typography } from '@mui/material'
import { makeStyles, MaskColorVar, useStylesExtends } from '@masknet/theme'
+import { InjectedDialog } from '@masknet/shared'
import { useValueRef, useRemoteControlledDialog } from '@masknet/shared-base-ui'
import { useI18N } from '../../../../utils'
import { SlippageSlider } from './SlippageSlider'
import { currentSlippageSettings } from '../../settings'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
import { PluginTraderMessages } from '../../messages'
import { ExpandMore } from '@mui/icons-material'
import { Gas1559Settings } from './Gas1559Settings'
diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/Trader.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/Trader.tsx
index c3accaad1d32..0e19cbd6c91e 100644
--- a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/Trader.tsx
+++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/Trader.tsx
@@ -17,6 +17,7 @@ import {
UST,
} from '@masknet/web3-shared-evm'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { usePickToken } from '@masknet/shared'
import { delay } from '@dimensiondev/kit'
import { useGasConfig } from './hooks/useGasConfig'
import type { Coin } from '../../types'
@@ -24,7 +25,7 @@ import { TokenPanelType, TradeInfo } from '../../types'
import { useI18N } from '../../../../utils'
import { TradeForm } from './TradeForm'
import { AllProviderTradeActionType, AllProviderTradeContext } from '../../trader/useAllProviderTradeContext'
-import { SelectTokenDialogEvent, WalletMessages } from '@masknet/plugin-wallet'
+import { WalletMessages } from '@masknet/plugin-wallet'
import { useUnmount, useUpdateEffect } from 'react-use'
import { isTwitter } from '../../../../social-network-adaptor/twitter.com/base'
import { activatedSocialNetworkUI } from '../../../../social-network'
@@ -204,43 +205,24 @@ export function Trader(props: TraderProps) {
// #region select token
const excludeTokens = [inputToken, outputToken].filter(Boolean).map((x) => x?.address) as string[]
- const [focusedTokenPanelType, setFocusedTokenPanelType] = useState(TokenPanelType.Input)
- const { setDialog: setSelectTokenDialog } = useRemoteControlledDialog(
- WalletMessages.events.selectTokenDialogUpdated,
- useCallback(
- (ev: SelectTokenDialogEvent) => {
- if (ev.open || !ev.token || ev.uuid !== String(focusedTokenPanelType)) return
+ const pickToken = usePickToken()
+ const onTokenChipClick = useCallback(
+ async (panelType: TokenPanelType) => {
+ const picked = await pickToken({
+ chainId,
+ disableNativeToken: false,
+ selectedTokens: excludeTokens,
+ })
+ if (picked) {
dispatchTradeStore({
type:
- focusedTokenPanelType === TokenPanelType.Input
+ panelType === TokenPanelType.Input
? AllProviderTradeActionType.UPDATE_INPUT_TOKEN
: AllProviderTradeActionType.UPDATE_OUTPUT_TOKEN,
- token: ev.token,
+ token: picked,
})
- if (focusedTokenPanelType === TokenPanelType.Input) {
- dispatchTradeStore({
- type: AllProviderTradeActionType.UPDATE_INPUT_AMOUNT,
- amount: '',
- })
- }
- },
- [dispatchTradeStore, focusedTokenPanelType],
- ),
- )
-
- const onTokenChipClick = useCallback(
- (type: TokenPanelType) => {
- setFocusedTokenPanelType(type)
- setSelectTokenDialog({
- chainId,
- open: true,
- uuid: String(type),
- disableNativeToken: false,
- FungibleTokenListProps: {
- selectedTokens: excludeTokens,
- },
- })
+ }
},
[excludeTokens.join(), chainId],
)
diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/TraderDialog.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/TraderDialog.tsx
index 9abcfc2213ae..e3ee9f2853f4 100644
--- a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/TraderDialog.tsx
+++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/TraderDialog.tsx
@@ -1,9 +1,9 @@
import { useEffect, useState } from 'react'
-import { usePluginIDContext, useActivatedPlugin, PluginId } from '@masknet/plugin-infra'
+import { useCurrentWeb3NetworkPluginID, useActivatedPlugin, PluginId } from '@masknet/plugin-infra'
import { ChainId, useChainId, useChainIdValid } from '@masknet/web3-shared-evm'
import { DialogContent } from '@mui/material'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog } from '@masknet/shared'
import { AllProviderTradeContext } from '../../trader/useAllProviderTradeContext'
import { TargetChainIdContext } from '../../trader/useTargetChainIdContext'
import { PluginTraderMessages } from '../../messages'
@@ -64,7 +64,7 @@ interface TraderDialogProps {
export function TraderDialog({ open, onClose }: TraderDialogProps) {
const isDashboard = isDashboardPage()
- const pluginID = usePluginIDContext()
+ const pluginID = useCurrentWeb3NetworkPluginID()
const traderDefinition = useActivatedPlugin(PluginId.Trader, 'any')
const chainIdList = traderDefinition?.enableRequirement.web3?.[pluginID]?.supportedChainIds ?? []
const { t } = useI18N()
diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trending/CoinMetadataTags.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trending/CoinMetadataTags.tsx
index 15addd3a130a..11577896ffa5 100644
--- a/packages/mask/src/plugins/Trader/SNSAdaptor/trending/CoinMetadataTags.tsx
+++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trending/CoinMetadataTags.tsx
@@ -1,7 +1,7 @@
import { Chip, DialogContent } from '@mui/material'
import { makeStyles } from '@masknet/theme'
import { useCallback, useState } from 'react'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import { Linking } from './Linking'
const useStyles = makeStyles()((theme) => ({
diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trending/PriceChart.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trending/PriceChart.tsx
index 3c15c3c6464b..bd58072d5ddc 100644
--- a/packages/mask/src/plugins/Trader/SNSAdaptor/trending/PriceChart.tsx
+++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trending/PriceChart.tsx
@@ -7,6 +7,7 @@ import { useI18N } from '../../../../utils'
import type { Coin, Currency, Stat } from '../../types'
import { useDimension, Dimension } from '../../../hooks/useDimension'
import { usePriceLineChart } from '../../../hooks/usePriceLineChart'
+import { openWindow } from '@masknet/shared-base-ui'
const DEFAULT_DIMENSION: Dimension = {
top: 32,
@@ -110,9 +111,7 @@ export function PriceChart(props: PriceChartProps) {
viewBox={`0 0 ${dimension.width} ${dimension.height}`}
preserveAspectRatio="xMidYMid meet"
onClick={() => {
- props.stats.length &&
- props.coin?.platform_url &&
- window.open(props.coin.platform_url, '_blank', 'noopener noreferrer')
+ props.stats.length && openWindow(props.coin?.platform_url)
}}
/>
>
diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trending/TrendingPopper.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trending/TrendingPopper.tsx
index 3e525596bedc..27642f95a731 100644
--- a/packages/mask/src/plugins/Trader/SNSAdaptor/trending/TrendingPopper.tsx
+++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trending/TrendingPopper.tsx
@@ -13,6 +13,8 @@ export interface TrendingPopperProps {
PopperProps?: Partial
}
+const TIMEOUT = 1500
+
export function TrendingPopper(props: TrendingPopperProps) {
const popperRef = useRef<{ update(): void } | null>(null)
const [freezed, setFreezed] = useState(false) // disable any click
@@ -21,13 +23,14 @@ export function TrendingPopper(props: TrendingPopperProps) {
const [type, setType] = useState()
const [anchorEl, setAnchorEl] = useState(null)
const [availableDataProviders, setAvailableDataProviders] = useState([])
+ const popper = useRef(null)
+ const [mouseIn, setMouseIn] = useState(false)
// #region select token and provider dialog could be open by trending view
const onFreezed = useCallback((ev) => setFreezed(ev.open), [])
useRemoteControlledDialog(WalletMessages.events.transactionDialogUpdated, onFreezed)
useRemoteControlledDialog(WalletMessages.events.walletStatusDialogUpdated, onFreezed)
useRemoteControlledDialog(WalletMessages.events.selectProviderDialogUpdated, onFreezed)
- useRemoteControlledDialog(WalletMessages.events.selectTokenDialogUpdated, onFreezed)
useRemoteControlledDialog(WalletMessages.events.selectWalletDialogUpdated, onFreezed)
useRemoteControlledDialog(WalletMessages.events.walletConnectQRCodeDialogUpdated, onFreezed)
useRemoteControlledDialog(PluginTransakMessages.buyTokenDialogUpdated, onFreezed)
@@ -67,14 +70,12 @@ export function TrendingPopper(props: TrendingPopperProps) {
// close popper if scroll out of visual screen
const position = useWindowScroll()
useEffect(() => {
- if (!anchorEl) return
- const { top } = anchorEl.getBoundingClientRect()
- if (
- top < 0 || // out off top bound
- top > document.documentElement.clientHeight // out off bottom bound
- )
+ if (!popper.current) return
+ const { top, height } = popper.current?.getBoundingClientRect()
+ if ((top < 0 && -1 * top > height) || top > document.documentElement.clientHeight)
+ // out off bottom bound
setAnchorEl(null)
- }, [anchorEl, Math.floor(position.y / 50)])
+ }, [popper, Math.floor(position.y / 50)])
// #endregion
if (locked) return null
@@ -85,6 +86,7 @@ export function TrendingPopper(props: TrendingPopperProps) {
if (!freezed) setAnchorEl(null)
}}>
},
+ ApplicationEntries: [
+ {
+ RenderEntryComponent({ disabled }) {
+ const { openDialog } = useRemoteControlledDialog(PluginTransakMessages.buyTokenDialogUpdated)
+
+ return (
+
+ )
+ },
+ defaultSortingPriority: 9,
+ },
+ ],
}
export default sns
diff --git a/packages/mask/src/components/shared/assets/fiat_ramp.png b/packages/mask/src/plugins/Transak/assets/fiat_ramp.png
similarity index 100%
rename from packages/mask/src/components/shared/assets/fiat_ramp.png
rename to packages/mask/src/plugins/Transak/assets/fiat_ramp.png
diff --git a/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/SelectRecipientsUnlockDialog.tsx b/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/SelectRecipientsUnlockDialog.tsx
index 2c204ced01da..ce26d0c2da63 100644
--- a/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/SelectRecipientsUnlockDialog.tsx
+++ b/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/SelectRecipientsUnlockDialog.tsx
@@ -1,4 +1,4 @@
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import Fuse from 'fuse.js'
import {
InputBase,
diff --git a/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/UnlockProtocolDialog.tsx b/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/UnlockProtocolDialog.tsx
index 6f33270650f5..d395b446a59a 100644
--- a/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/UnlockProtocolDialog.tsx
+++ b/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/UnlockProtocolDialog.tsx
@@ -1,7 +1,7 @@
import { useAccount, useChainId } from '@masknet/web3-shared-evm'
import { DialogActions, DialogContent, DialogProps, Chip, Button, InputBase } from '@mui/material'
import { useEffect, useState } from 'react'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import { useI18N } from '../../../utils'
import { pluginMetaKey } from '../constants'
import type { UnlockLocks } from '../types'
diff --git a/packages/mask/src/plugins/UnlockProtocol/base.ts b/packages/mask/src/plugins/UnlockProtocol/base.ts
index b6be16f2bda6..7b29c145ed32 100644
--- a/packages/mask/src/plugins/UnlockProtocol/base.ts
+++ b/packages/mask/src/plugins/UnlockProtocol/base.ts
@@ -16,6 +16,8 @@ export const base: Plugin.Shared.Definition = {
[NetworkPluginID.PLUGIN_EVM]: {
supportedChainIds: [ChainId.Mainnet, ChainId.xDai, ChainId.Matic, ChainId.Rinkeby],
},
+ [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] },
+ [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] },
},
},
contribution: {
diff --git a/packages/mask/src/plugins/Wallet/Dashboard/index.tsx b/packages/mask/src/plugins/Wallet/Dashboard/index.tsx
index 9a70a55c4cc2..780608eec12f 100644
--- a/packages/mask/src/plugins/Wallet/Dashboard/index.tsx
+++ b/packages/mask/src/plugins/Wallet/Dashboard/index.tsx
@@ -1,6 +1,5 @@
import type { Plugin } from '@masknet/plugin-infra'
import { base } from '@masknet/plugin-wallet'
-import { SelectTokenDialog } from '../SNSAdaptor/SelectTokenDialog'
import { SelectNftContractDialog } from '../SNSAdaptor/SelectNftContractDialog'
import { SelectProviderDialog } from '../SNSAdaptor/SelectProviderDialog'
import { SelectWalletDialog } from '../SNSAdaptor/SelectWalletDialog'
@@ -23,7 +22,6 @@ const dashboard: Plugin.Dashboard.Definition = {
-
@@ -31,7 +29,6 @@ const dashboard: Plugin.Dashboard.Definition = {
-
>
diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx
index becd7b16a80d..866f5eae1503 100644
--- a/packages/mask/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx
+++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx
@@ -13,7 +13,7 @@ import {
resolveProviderName,
} from '@masknet/web3-shared-evm'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import { WalletMessages, WalletRPC } from '../../messages'
import { ConnectionProgress } from './ConnectionProgress'
import Services from '../../../../extension/service'
diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/GasSettingDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/GasSettingDialog/index.tsx
index 783e51eba5c8..62f0ba6d1d5c 100644
--- a/packages/mask/src/plugins/Wallet/SNSAdaptor/GasSettingDialog/index.tsx
+++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/GasSettingDialog/index.tsx
@@ -3,8 +3,8 @@ import { DialogContent } from '@mui/material'
import { WalletMessages } from '@masknet/plugin-wallet'
import { makeStyles } from '@masknet/theme'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog } from '@masknet/shared'
import { GasOption } from '@masknet/web3-shared-evm'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
import { useI18N } from '../../../../utils'
import { GasSetting } from './GasSetting'
diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/RenameWalletDialog.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/RenameWalletDialog.tsx
index 05e5c2826060..804abf70cac7 100644
--- a/packages/mask/src/plugins/Wallet/SNSAdaptor/RenameWalletDialog.tsx
+++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/RenameWalletDialog.tsx
@@ -3,10 +3,9 @@ import { Button, DialogContent, DialogActions, TextField } from '@mui/material'
import { makeStyles } from '@masknet/theme'
import type { Wallet } from '@masknet/web3-shared-evm'
import { WalletMessages, WalletRPC } from '../messages'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
-import { useI18N } from '../../../utils/i18n-next-ui'
-import { useSnackbarCallback } from '@masknet/shared'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog, useSnackbarCallback } from '@masknet/shared'
+import { useI18N } from '../../../utils/i18n-next-ui'
const useStyles = makeStyles()((theme) => ({
content: {
diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/RestoreLegacyWalletDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/RestoreLegacyWalletDialog/index.tsx
index 5c3ef92a33fc..25d21fd38af3 100644
--- a/packages/mask/src/plugins/Wallet/SNSAdaptor/RestoreLegacyWalletDialog/index.tsx
+++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/RestoreLegacyWalletDialog/index.tsx
@@ -1,12 +1,12 @@
import { useCallback, useEffect } from 'react'
import { useAsyncRetry } from 'react-use'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog } from '@masknet/shared'
import { Box } from '@mui/system'
import { makeStyles } from '@masknet/theme'
import { ProviderType } from '@masknet/web3-shared-evm'
import { Button, DialogContent, Typography } from '@mui/material'
import { PopupRoutes } from '@masknet/shared-base'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
import { WalletMessages, WalletRPC } from '../../messages'
import { useI18N } from '../../../../utils'
diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/RiskWarningDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/RiskWarningDialog/index.tsx
index 28ff99b7d217..0c01a034f884 100644
--- a/packages/mask/src/plugins/Wallet/SNSAdaptor/RiskWarningDialog/index.tsx
+++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/RiskWarningDialog/index.tsx
@@ -2,11 +2,11 @@ import { useCallback } from 'react'
import classnames from 'classnames'
import { Trans } from 'react-i18next'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog } from '@masknet/shared'
import { formatEthereumAddress, useAccount } from '@masknet/web3-shared-evm'
import PriorityHighIcon from '@mui/icons-material/PriorityHigh'
import { Avatar, Button, DialogActions, DialogContent, Paper, Typography } from '@mui/material'
import { getMaskColor, makeStyles, useCustomSnackbar } from '@masknet/theme'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
import { useI18N, useMatchXS } from '../../../../utils'
import { WalletMessages, WalletRPC } from '../../messages'
import { ActionButtonPromise } from '../../../../extension/options-page/DashboardComponents/ActionButton'
diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx
index 2572cabda523..a0fb39bda34e 100644
--- a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx
+++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx
@@ -13,10 +13,10 @@ import {
useERC721ContractDetailed,
useERC721Tokens,
} from '@masknet/web3-shared-evm'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog } from '@masknet/shared'
import { WalletMessages } from '../messages'
import { useI18N } from '../../../utils'
-import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
import { EthereumAddress } from 'wallet.ts'
import { SearchInput } from '../../../extension/options-page/DashboardComponents/SearchInput'
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectProviderDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectProviderDialog/index.tsx
index a76885ba440c..f3dadd8190a5 100644
--- a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectProviderDialog/index.tsx
+++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectProviderDialog/index.tsx
@@ -1,6 +1,8 @@
import { useCallback, useEffect, useState } from 'react'
import { makeStyles } from '@masknet/theme'
import { DialogContent } from '@mui/material'
+import { useRemoteControlledDialog, useValueRef } from '@masknet/shared-base-ui'
+import { InjectedDialog } from '@masknet/shared'
import {
getRegisteredWeb3Networks,
getRegisteredWeb3Providers,
@@ -10,10 +12,8 @@ import {
useWeb3UI,
} from '@masknet/plugin-infra'
import { isDashboardPage } from '@masknet/shared-base'
-import { useRemoteControlledDialog, useValueRef } from '@masknet/shared-base-ui'
import { useI18N } from '../../../../utils/i18n-next-ui'
import { WalletMessages } from '../../messages'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
import { hasNativeAPI, nativeAPI } from '../../../../../shared/native-rpc'
import { PluginProviderRender } from './PluginProviderRender'
import { pluginIDSettings } from '../../../../settings/settings'
@@ -51,7 +51,7 @@ export function SelectProviderDialog(props: SelectProviderDialogProps) {
const pluginID = useValueRef(pluginIDSettings) as NetworkPluginID
const network = useNetworkDescriptor()
const [undeterminedPluginID, setUndeterminedPluginID] = useState(pluginID)
- const [undeterminedNetworkID, setUndeterminedNetworkID] = useState(network?.ID)
+ const [undeterminedNetworkID, setUndeterminedNetworkID] = useState(network?.ID ?? NetworkPluginID.PLUGIN_EVM)
const undeterminedNetwork = useNetworkDescriptor(undeterminedNetworkID, undeterminedPluginID)
const networkType = useNetworkType(undeterminedPluginID)
diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectTokenDialog.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectTokenDialog.tsx
deleted file mode 100644
index 73d036761b4e..000000000000
--- a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectTokenDialog.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import { useCallback, useEffect, useState } from 'react'
-import { DialogContent, Theme, useMediaQuery } from '@mui/material'
-import { makeStyles, MaskColorVar, useStylesExtends } from '@masknet/theme'
-import { FungibleTokenDetailed, useChainId, ChainId, useTokenConstants } from '@masknet/web3-shared-evm'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
-import { WalletMessages } from '../../Wallet/messages'
-import { useI18N } from '../../../utils'
-import { ERC20TokenList, ERC20TokenListProps } from '@masknet/shared'
-import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
-import { delay } from '@dimensiondev/kit'
-import { MINDS_ID } from '../../../social-network-adaptor/minds.com/base'
-import { activatedSocialNetworkUI } from '../../../social-network'
-
-interface StyleProps {
- snsId: string
- isDashboard: boolean
-}
-
-const useStyles = makeStyles()((theme, { snsId, isDashboard }) => ({
- content: {
- ...(snsId === MINDS_ID ? { minWidth: 552 } : {}),
- padding: theme.spacing(3),
- paddingTop: isDashboard ? 0 : theme.spacing(2.8),
- },
- list: {
- scrollbarWidth: 'none',
- '&::-webkit-scrollbar': {
- display: 'none',
- },
- },
- placeholder: {
- textAlign: 'center',
- height: 288,
- paddingTop: theme.spacing(14),
- boxSizing: 'border-box',
- },
- search: {
- backgroundColor: 'transparent !important',
- border: `solid 1px ${MaskColorVar.twitterBorderLine}`,
- },
-}))
-
-export interface SelectTokenDialogProps extends withClasses {}
-
-export function SelectTokenDialog(props: SelectTokenDialogProps) {
- const { t } = useI18N()
- const isDashboard = location.href.includes('dashboard.html')
- const classes = useStylesExtends(
- useStyles({ snsId: activatedSocialNetworkUI.networkIdentifier, isDashboard }),
- props,
- )
- const chainId = useChainId()
- const { NATIVE_TOKEN_ADDRESS } = useTokenConstants(chainId)
- const isMdScreen = useMediaQuery((theme) => theme.breakpoints.down('md'))
-
- const [id, setId] = useState('')
- const [targetChainId, setChainId] = useState(chainId)
- const [rowSize, setRowSize] = useState(54)
-
- const [title, setTitle] = useState(t('plugin_wallet_select_a_token'))
- const [disableNativeToken, setDisableNativeToken] = useState(true)
- const [disableSearchBar, setDisableSearchBar] = useState(false)
- const [FungibleTokenListProps, setFungibleTokenListProps] = useState(null)
-
- useEffect(() => {
- try {
- const fontSize = Number.parseFloat(getComputedStyle(document.documentElement).fontSize)
- setRowSize(fontSize * 4)
- } catch {
- setRowSize(60)
- }
- }, [])
-
- const { open, setDialog } = useRemoteControlledDialog(WalletMessages.events.selectTokenDialogUpdated, (ev) => {
- if (!ev.open) return
- setTitle(ev.title ?? t('plugin_wallet_select_a_token'))
- setId(ev.uuid)
- setDisableNativeToken(ev.disableNativeToken ?? true)
- setDisableSearchBar(ev.disableSearchBar ?? false)
- setFungibleTokenListProps(ev.FungibleTokenListProps ?? null)
- setChainId(ev.chainId ?? undefined)
- })
- const onSubmit = useCallback(
- async (token: FungibleTokenDetailed) => {
- setDialog({
- open: false,
- uuid: id,
- token,
- })
- await delay(300)
- },
- [id, setDialog],
- )
- const onClose = useCallback(async () => {
- setDialog({
- open: false,
- uuid: id,
- })
- await delay(300)
- }, [id, setDialog])
-
- return (
-
-
-
-
-
- )
-}
diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectWalletDialog.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectWalletDialog.tsx
index c661e2bc02d9..c02c08b27c72 100644
--- a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectWalletDialog.tsx
+++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectWalletDialog.tsx
@@ -3,12 +3,12 @@ import { Button, DialogActions, DialogContent } from '@mui/material'
import { makeStyles, useStylesExtends } from '@masknet/theme'
import { ProviderType, useWallets, useWallet, NetworkType } from '@masknet/web3-shared-evm'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog } from '@masknet/shared'
import { DashboardRoutes } from '@masknet/shared-base'
import { useI18N } from '../../../utils'
import { WalletMessages, WalletRPC } from '../messages'
import { WalletInList } from '../../../components/shared/SelectWallet/WalletInList'
import Services from '../../../extension/service'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
import { delay } from '@dimensiondev/kit'
const useStyles = makeStyles()({
diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/TransactionDialog.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/TransactionDialog.tsx
index 3d0d3a010904..4e819e6cdac7 100644
--- a/packages/mask/src/plugins/Wallet/SNSAdaptor/TransactionDialog.tsx
+++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/TransactionDialog.tsx
@@ -9,9 +9,9 @@ import {
TransactionStateType,
resolveTransactionLinkOnExplorer,
} from '@masknet/web3-shared-evm'
-import { useI18N } from '../../../utils'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
+import { useI18N } from '../../../utils'
import { WalletMessages } from '../messages'
import { activatedSocialNetworkUI } from '../../../social-network'
diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/SafariPlatform.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/SafariPlatform.tsx
index f560d8a699b3..7076453818c2 100644
--- a/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/SafariPlatform.tsx
+++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/SafariPlatform.tsx
@@ -5,6 +5,8 @@ import { createElement } from 'react'
import { useI18N } from '../../../../utils'
import { Provider } from '../Provider'
import { IMTokenIcon, MetaMaskIcon, RainbowIcon, TrustIcon } from './Icons'
+import urlcat from 'urlcat'
+import { openWindow } from '@masknet/shared-base-ui'
const useStyles = makeStyles()({
container: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' },
@@ -27,11 +29,7 @@ const providers: WalletProvider[] = [
export const SafariPlatform: React.FC<{ uri: string }> = ({ uri }) => {
const { t } = useI18N()
const { classes } = useStyles()
- const makeConnect = (link: string) => () => {
- const url = new URL(link)
- url.searchParams.set('uri', uri)
- open(url.toString())
- }
+ const makeConnect = (link: string) => () => openWindow(urlcat(link, { uri }))
const descriptionMapping: Record = {
MetaMask: t('plugin_wallet_connect_safari_metamask'),
Rainbow: t('plugin_wallet_connect_safari_rainbow'),
diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/index.tsx
index f84c6c711972..7de9b3a09cc9 100644
--- a/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/index.tsx
+++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/index.tsx
@@ -1,11 +1,11 @@
import { useState } from 'react'
import { Button, DialogActions, DialogContent } from '@mui/material'
import { makeStyles } from '@masknet/theme'
-import { useI18N } from '../../../../utils'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog } from '@masknet/shared'
+import { useI18N } from '../../../../utils'
import { WalletMessages } from '../../messages'
import Services from '../../../../extension/service'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
import { SafariPlatform } from './SafariPlatform'
import { FirefoxPlatform } from './FirefoxPlatform'
import { QRCodeModel } from './QRCodeModel'
diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletStatusDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletStatusDialog/index.tsx
index 90227a1f8b07..2e2400361dbb 100644
--- a/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletStatusDialog/index.tsx
+++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletStatusDialog/index.tsx
@@ -2,13 +2,13 @@ import { useCallback } from 'react'
import { DialogActions, DialogContent, Typography } from '@mui/material'
import ErrorIcon from '@mui/icons-material/Error'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { InjectedDialog } from '@masknet/shared'
+import { CrossIsolationMessages } from '@masknet/shared-base'
import { useChainIdValid } from '@masknet/web3-shared-evm'
import { makeStyles } from '@masknet/theme'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
import { WalletStatusBox } from '../../../../components/shared/WalletStatusBox'
import { useI18N } from '../../../../utils'
import { WalletMessages } from '../../messages'
-import { MaskMessages } from '../../../../utils/messages'
import { ApplicationBoard } from '../../../../components/shared/ApplicationBoard'
const useStyles = makeStyles()((theme) => ({
@@ -51,7 +51,7 @@ export function WalletStatusDialog(props: WalletStatusDialogProps) {
const closeDialog = useCallback(() => {
_closeDialog()
- MaskMessages.events.requestComposition.sendToLocal({
+ CrossIsolationMessages.events.requestComposition.sendToLocal({
reason: 'timeline',
open: false,
})
diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/index.tsx
index 954cdf2aa90f..25751a58a88b 100644
--- a/packages/mask/src/plugins/Wallet/SNSAdaptor/index.tsx
+++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/index.tsx
@@ -1,6 +1,5 @@
import type { Plugin } from '@masknet/plugin-infra'
import { base } from '@masknet/plugin-wallet'
-import { SelectTokenDialog } from './SelectTokenDialog'
import { SelectNftContractDialog } from './SelectNftContractDialog'
import { SelectProviderDialog } from './SelectProviderDialog'
import { SelectWalletDialog } from './SelectWalletDialog'
@@ -23,7 +22,6 @@ const sns: Plugin.SNSAdaptor.Definition = {
-
diff --git a/packages/mask/src/plugins/Wallet/messages.ts b/packages/mask/src/plugins/Wallet/messages.ts
index ff23a6a24d43..3d03a916363a 100644
--- a/packages/mask/src/plugins/Wallet/messages.ts
+++ b/packages/mask/src/plugins/Wallet/messages.ts
@@ -3,7 +3,7 @@ import { PLUGIN_ID, WalletMessages } from '@masknet/plugin-wallet'
import type { _AsyncVersionOf } from 'async-call-rpc'
export { WalletMessages } from '@masknet/plugin-wallet'
-export type { SelectTokenDialogEvent, SelectNftContractDialogEvent } from '@masknet/plugin-wallet'
+export type { SelectNftContractDialogEvent } from '@masknet/plugin-wallet'
export const WalletRPC: _AsyncVersionOf = createPluginRPC(
PLUGIN_ID,
() => import('./services') as any,
diff --git a/packages/mask/src/plugins/Wallet/services/assets.ts b/packages/mask/src/plugins/Wallet/services/assets.ts
index 8cb0df11296f..1d1c8c2c3915 100644
--- a/packages/mask/src/plugins/Wallet/services/assets.ts
+++ b/packages/mask/src/plugins/Wallet/services/assets.ts
@@ -143,6 +143,7 @@ export async function getAssetsList(
function formatAssetsFromDebank(data: WalletTokenRecord[], network?: NetworkType) {
return data
+ .filter((x) => getChainIdFromName(x.chain))
.filter((x) => !network || getChainIdFromName(x.chain) === getChainIdFromNetworkType(network))
.filter((x) => x.is_verified)
.map((y): Asset => {
diff --git a/packages/mask/src/plugins/dHEDGE/UI/InvestDialog.tsx b/packages/mask/src/plugins/dHEDGE/UI/InvestDialog.tsx
index 96e30a9465bd..88cf755db47f 100644
--- a/packages/mask/src/plugins/dHEDGE/UI/InvestDialog.tsx
+++ b/packages/mask/src/plugins/dHEDGE/UI/InvestDialog.tsx
@@ -1,3 +1,7 @@
+import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { usePickToken, InjectedDialog } from '@masknet/shared'
+import { makeStyles } from '@masknet/theme'
+import { isZero, rightShift } from '@masknet/web3-shared-base'
import {
EthereumTokenType,
formatBalance,
@@ -6,24 +10,20 @@ import {
useAccount,
useFungibleTokenBalance,
} from '@masknet/web3-shared-evm'
-import { isZero, rightShift } from '@masknet/web3-shared-base'
import { DialogContent } from '@mui/material'
-import { makeStyles } from '@masknet/theme'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { v4 as uuid } from 'uuid'
-import { InjectedDialog } from '../../../components/shared/InjectedDialog'
import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton'
import { activatedSocialNetworkUI } from '../../../social-network'
-import { isTwitter } from '../../../social-network-adaptor/twitter.com/base'
import { isFacebook } from '../../../social-network-adaptor/facebook.com/base'
-import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
+import { isTwitter } from '../../../social-network-adaptor/twitter.com/base'
import { useI18N } from '../../../utils/i18n-next-ui'
import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary'
import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary'
import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel'
import { PluginTraderMessages } from '../../Trader/messages'
import type { Coin } from '../../Trader/types'
-import { SelectTokenDialogEvent, WalletMessages } from '../../Wallet/messages'
+import { WalletMessages } from '../../Wallet/messages'
import { useInvestCallback } from '../hooks/useInvestCallback'
import { PluginDHedgeMessages } from '../messages'
import type { Pool } from '../types'
@@ -54,7 +54,7 @@ const useStyles = makeStyles()((theme) => ({
export function InvestDialog() {
const { t } = useI18N()
const { classes } = useStyles()
- const [id] = useState(uuid())
+ const [id] = useState(uuid)
const [pool, setPool] = useState()
const [token, setToken] = useState()
const [allowedTokens, setAllowedTokens] = useState()
@@ -77,26 +77,14 @@ export function InvestDialog() {
// #endregion
// #region select token
- const { setDialog: setSelectTokenDialogOpen } = useRemoteControlledDialog(
- WalletMessages.events.selectTokenDialogUpdated,
- useCallback(
- (ev: SelectTokenDialogEvent) => {
- if (ev.open || !ev.token || ev.uuid !== id) return
- setToken(ev.token)
- },
- [id],
- ),
- )
- const onSelectTokenChipClick = useCallback(() => {
- setSelectTokenDialogOpen({
- open: true,
- uuid: id,
+ const pickToken = usePickToken()
+ const onSelectTokenChipClick = useCallback(async () => {
+ const picked = await pickToken({
disableNativeToken: true,
- FungibleTokenListProps: {
- whitelist: allowedTokens,
- },
+ whitelist: allowedTokens,
})
- }, [id, token?.address, allowedTokens])
+ if (picked) setToken(picked)
+ }, [pickToken, token?.address, allowedTokens])
// #endregion
// #region amount
diff --git a/packages/mask/src/social-network-adaptor/facebook.com/automation/openComposeBox.ts b/packages/mask/src/social-network-adaptor/facebook.com/automation/openComposeBox.ts
index 2bd58acc75d6..1e789aee829b 100644
--- a/packages/mask/src/social-network-adaptor/facebook.com/automation/openComposeBox.ts
+++ b/packages/mask/src/social-network-adaptor/facebook.com/automation/openComposeBox.ts
@@ -1,5 +1,5 @@
import { LiveSelector } from '@dimensiondev/holoflows-kit'
-import { MaskMessages, CompositionRequest } from '../../../utils/messages'
+import { CrossIsolationMessages, CompositionRequest } from '@masknet/shared-base'
import { i18n } from '../../../../shared-ui/locales_legacy'
import { makeTypedMessageText, SerializableTypedMessages } from '@masknet/typed-message'
import { delay, waitDocumentReadyState } from '@dimensiondev/kit'
@@ -56,7 +56,7 @@ export async function taskOpenComposeBoxFacebook(
}
await delay(2000)
- MaskMessages.events.requestComposition.sendToLocal({
+ CrossIsolationMessages.events.requestComposition.sendToLocal({
reason: 'popup',
open: true,
content: typeof content === 'string' ? makeTypedMessageText(content) : content,
diff --git a/packages/mask/src/social-network-adaptor/facebook.com/injection/Composition.tsx b/packages/mask/src/social-network-adaptor/facebook.com/injection/Composition.tsx
index 7ebdf51ce00b..1ee7a87a0858 100644
--- a/packages/mask/src/social-network-adaptor/facebook.com/injection/Composition.tsx
+++ b/packages/mask/src/social-network-adaptor/facebook.com/injection/Composition.tsx
@@ -1,10 +1,10 @@
import { useCallback } from 'react'
import { LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit'
+import { CrossIsolationMessages } from '@masknet/shared-base'
import { createReactRootShadowed } from '../../../utils/shadow-root/renderInShadowRoot'
import { Composition } from '../../../components/CompositionDialog/Composition'
import { isMobileFacebook } from '../utils/isMobile'
import { PostDialogHint } from '../../../components/InjectedComponents/PostDialogHint'
-import { MaskMessages } from '../../../utils/messages'
import { startWatch } from '../../../utils/watcher'
import { taskOpenComposeBoxFacebook } from '../automation/openComposeBox'
import { makeStyles } from '@masknet/theme'
@@ -53,7 +53,7 @@ export function injectCompositionFacebook(signal: AbortSignal) {
signal.addEventListener(
'abort',
- MaskMessages.events.requestComposition.on((data) => {
+ CrossIsolationMessages.events.requestComposition.on((data) => {
if (data.reason === 'popup') return
if (data.open === false) return
taskOpenComposeBoxFacebook(data.content || '', data.options)
@@ -63,7 +63,7 @@ export function injectCompositionFacebook(signal: AbortSignal) {
function UI() {
const { classes } = useStyles()
const onHintButtonClicked = useCallback(
- () => MaskMessages.events.requestComposition.sendToLocal({ reason: 'popup', open: true }),
+ () => CrossIsolationMessages.events.requestComposition.sendToLocal({ reason: 'popup', open: true }),
[],
)
return (
diff --git a/packages/mask/src/social-network-adaptor/facebook.com/injection/NFT/NFTAvatarEditProfile.tsx b/packages/mask/src/social-network-adaptor/facebook.com/injection/NFT/NFTAvatarEditProfile.tsx
index 1280efe19771..fc0caf2bf5c7 100644
--- a/packages/mask/src/social-network-adaptor/facebook.com/injection/NFT/NFTAvatarEditProfile.tsx
+++ b/packages/mask/src/social-network-adaptor/facebook.com/injection/NFT/NFTAvatarEditProfile.tsx
@@ -1,8 +1,7 @@
import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit'
import { searchFacebookEditProfileSelector, searchFacebookProfileSettingButtonSelector } from '../../utils/selector'
-import { createReactRootShadowed, startWatch } from '../../../../utils'
+import { createReactRootShadowed, startWatch, useLocationChange } from '../../../../utils'
import { useLayoutEffect, useState } from 'react'
-import { useLocationChange } from '../../../../utils/hooks/useLocationChange'
import { NFTAvatarButton } from '../../../../plugins/Avatar/SNSAdaptor/NFTAvatarButton'
import { makeStyles } from '@masknet/theme'
diff --git a/packages/mask/src/social-network-adaptor/facebook.com/shared.ts b/packages/mask/src/social-network-adaptor/facebook.com/shared.ts
index 18a3e5fa00c0..305dcbc30890 100644
--- a/packages/mask/src/social-network-adaptor/facebook.com/shared.ts
+++ b/packages/mask/src/social-network-adaptor/facebook.com/shared.ts
@@ -4,6 +4,8 @@ import { getPostUrlAtFacebook, isValidFacebookUsername } from './utils/parse-use
import { PostIdentifier, ProfileIdentifier } from '@masknet/shared-base'
import { deconstructPayload } from '../../utils'
import { createSNSAdaptorSpecializedPostContext } from '../../social-network/utils/create-post-context'
+import { openWindow } from '@masknet/shared-base-ui'
+import urlcat from 'urlcat'
const getPostURL = (post: PostIdentifier): URL | null => {
if (post.identifier instanceof ProfileIdentifier)
@@ -19,14 +21,14 @@ export const facebookShared: SocialNetwork.Shared & SocialNetwork.Base = {
textPayloadPostProcessor: undefined,
getPostURL,
share(message) {
- const url = this.getShareLinkURL!(message)
- window.open(url, '_blank', 'noopener noreferrer')
+ openWindow(this.getShareLinkURL?.(message))
},
getShareLinkURL(message) {
- const url = new URL('https://www.facebook.com/sharer/sharer.php')
- url.searchParams.set('quote', message)
- url.searchParams.set('u', 'mask.io')
- return url
+ const url = urlcat('https://www.facebook.com/sharer/sharer.php', {
+ quote: message,
+ u: 'mask.io',
+ })
+ return new URL(url)
},
createPostContext: createSNSAdaptorSpecializedPostContext({
payloadParser: deconstructPayload,
diff --git a/packages/mask/src/social-network-adaptor/facebook.com/ui-provider.ts b/packages/mask/src/social-network-adaptor/facebook.com/ui-provider.ts
index f10fb500ca71..842709e8305c 100644
--- a/packages/mask/src/social-network-adaptor/facebook.com/ui-provider.ts
+++ b/packages/mask/src/social-network-adaptor/facebook.com/ui-provider.ts
@@ -146,10 +146,12 @@ const facebookUI: SocialNetworkUI.Definition = {
},
customization: {
paletteMode: PaletteModeProviderFacebook,
- componentOverwrite: {
+ sharedComponentOverwrite: {
InjectedDialog: {
classes: useInjectedDialogClassesOverwriteFacebook,
},
+ },
+ componentOverwrite: {
RenderFragments: FacebookRenderFragments,
},
useTheme: useThemeFacebookVariant,
diff --git a/packages/mask/src/social-network-adaptor/instagram.com/base.ts b/packages/mask/src/social-network-adaptor/instagram.com/base.ts
index 54bbad8276e2..3135f0cde0c2 100644
--- a/packages/mask/src/social-network-adaptor/instagram.com/base.ts
+++ b/packages/mask/src/social-network-adaptor/instagram.com/base.ts
@@ -1,17 +1,17 @@
+import { INSTAGRAM_ID } from '@masknet/shared'
import { SocialNetworkEnum } from '@masknet/encryption'
import type { SocialNetwork, SocialNetworkWorker } from '../../social-network/types'
-const id = 'instagram.com'
const origins = ['https://www.instagram.com/*', 'https://m.instagram.com/*', 'https://instagram.com/*']
export const instagramBase: SocialNetwork.Base = {
- networkIdentifier: id,
+ networkIdentifier: INSTAGRAM_ID,
encryptionNetwork: SocialNetworkEnum.Instagram,
declarativePermissions: { origins },
shouldActivate(location) {
- return location.host.endsWith(id)
+ return location.host.endsWith(INSTAGRAM_ID)
},
}
export const instagramWorkerBase: SocialNetworkWorker.WorkerBase & SocialNetwork.Base = {
...instagramBase,
- gunNetworkHint: id,
+ gunNetworkHint: INSTAGRAM_ID,
}
diff --git a/packages/mask/src/social-network-adaptor/instagram.com/injection/Entry.tsx b/packages/mask/src/social-network-adaptor/instagram.com/injection/Entry.tsx
index eef3f66a41ab..f817252195cc 100644
--- a/packages/mask/src/social-network-adaptor/instagram.com/injection/Entry.tsx
+++ b/packages/mask/src/social-network-adaptor/instagram.com/injection/Entry.tsx
@@ -1,9 +1,9 @@
import { Fab, styled } from '@mui/material'
import { Create } from '@mui/icons-material'
+import { CrossIsolationMessages } from '@masknet/shared-base'
import { Composition } from '../../../components/CompositionDialog/Composition'
import { useState, useEffect } from 'react'
import { LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit'
-import { MaskMessages } from '../../../utils'
const Container = styled('div')`
position: fixed;
@@ -30,7 +30,7 @@ export function Entry() {
{
- MaskMessages.events.requestComposition.sendToLocal({ open: true, reason: 'timeline' })
+ CrossIsolationMessages.events.requestComposition.sendToLocal({ open: true, reason: 'timeline' })
}}>
Create with Mask
diff --git a/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/NFTAvatarSettingDialog.tsx b/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/NFTAvatarSettingDialog.tsx
index 3442a7af4232..3f350a513860 100644
--- a/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/NFTAvatarSettingDialog.tsx
+++ b/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/NFTAvatarSettingDialog.tsx
@@ -4,7 +4,7 @@ import { useCurrentVisitingIdentity } from '../../../../components/DataSource/us
import { toPNG } from '../../../../plugins/Avatar/utils'
import { useMount } from 'react-use'
import { getAvatarId } from '../../utils/user'
-import { InjectedDialog } from '../../../../components/shared/InjectedDialog'
+import { InjectedDialog } from '@masknet/shared'
import { DialogContent } from '@mui/material'
import { NFTAvatar } from '../../../../plugins/Avatar/SNSAdaptor/NFTAvatar'
import { DialogStackingProvider, makeStyles } from '@masknet/theme'
diff --git a/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/ProfileNFTAvatar.tsx b/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/ProfileNFTAvatar.tsx
index 5956adb2f5c8..4b3fd6fc0e95 100644
--- a/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/ProfileNFTAvatar.tsx
+++ b/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/ProfileNFTAvatar.tsx
@@ -1,9 +1,8 @@
import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit'
import { searchInstagramAvatarListSelector } from '../../utils/selector'
-import { createReactRootShadowed, MaskMessages, startWatch, useI18N } from '../../../../utils'
+import { createReactRootShadowed, MaskMessages, startWatch, useI18N, useLocationChange } from '../../../../utils'
import { makeStyles } from '@masknet/theme'
import { useCallback, useLayoutEffect, useState } from 'react'
-import { useLocationChange } from '../../../../utils/hooks/useLocationChange'
import { useLocation } from 'react-use'
export async function injectProfileNFTAvatarInInstagram(signal: AbortSignal) {
diff --git a/packages/mask/src/social-network-adaptor/instagram.com/injection/ProfileTab.tsx b/packages/mask/src/social-network-adaptor/instagram.com/injection/ProfileTab.tsx
index 4bca7610b3c3..f52a94f53e5c 100644
--- a/packages/mask/src/social-network-adaptor/instagram.com/injection/ProfileTab.tsx
+++ b/packages/mask/src/social-network-adaptor/instagram.com/injection/ProfileTab.tsx
@@ -151,7 +151,7 @@ export function ProfileTabAtInstagram() {
ele.style.display = ''
}
}
- const clear = async () => {
+ const clear = () => {
const style = getStyleProps({ activeColor, color })
const activeTab = searchProfileActiveTabSelector().evaluate()
if (activeTab?.style) {
diff --git a/packages/mask/src/social-network-adaptor/minds.com/automation/openComposeBox.ts b/packages/mask/src/social-network-adaptor/minds.com/automation/openComposeBox.ts
index 660925f17e3b..8194b804fa08 100644
--- a/packages/mask/src/social-network-adaptor/minds.com/automation/openComposeBox.ts
+++ b/packages/mask/src/social-network-adaptor/minds.com/automation/openComposeBox.ts
@@ -1,7 +1,7 @@
import { makeTypedMessageText, SerializableTypedMessages } from '@masknet/typed-message'
+import { CrossIsolationMessages, CompositionRequest } from '@masknet/shared-base'
import { delay, waitDocumentReadyState } from '@dimensiondev/kit'
import { i18n } from '../../../../shared-ui/locales_legacy'
-import { MaskMessages, CompositionRequest } from '../../../utils/messages'
import { composeButtonSelector, composeDialogIndicatorSelector, composeTextareaSelector } from '../utils/selector'
export async function openComposeBoxMinds(
@@ -26,7 +26,7 @@ export async function openComposeBoxMinds(
}
await delay(800)
- MaskMessages.events.requestComposition.sendToLocal({
+ CrossIsolationMessages.events.requestComposition.sendToLocal({
reason: 'popup',
open: true,
content: typeof content === 'string' ? makeTypedMessageText(content) : content,
diff --git a/packages/mask/src/social-network-adaptor/minds.com/base.ts b/packages/mask/src/social-network-adaptor/minds.com/base.ts
index e827f3b6372b..33027b5eb400 100644
--- a/packages/mask/src/social-network-adaptor/minds.com/base.ts
+++ b/packages/mask/src/social-network-adaptor/minds.com/base.ts
@@ -1,7 +1,7 @@
+import { MINDS_ID } from '@masknet/shared'
import { SocialNetworkEnum } from '@masknet/encryption'
import type { SocialNetwork, SocialNetworkWorker } from '../../social-network/types'
-export const MINDS_ID = 'minds.com'
const origins = ['https://www.minds.com/*', 'https://minds.com/*', 'https://cdn.minds.com/*']
export const mindsBase: SocialNetwork.Base = {
networkIdentifier: MINDS_ID,
diff --git a/packages/mask/src/social-network-adaptor/minds.com/injection/PostDialogHint.tsx b/packages/mask/src/social-network-adaptor/minds.com/injection/PostDialogHint.tsx
index 78bc4a0e4338..88c409cabefd 100644
--- a/packages/mask/src/social-network-adaptor/minds.com/injection/PostDialogHint.tsx
+++ b/packages/mask/src/social-network-adaptor/minds.com/injection/PostDialogHint.tsx
@@ -1,8 +1,8 @@
import { LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit'
import { makeStyles } from '@masknet/theme'
+import { CrossIsolationMessages } from '@masknet/shared-base'
import { useCallback } from 'react'
import { PostDialogHint } from '../../../components/InjectedComponents/PostDialogHint'
-import { MaskMessages } from '../../../utils/messages'
import { createReactRootShadowed } from '../../../utils/shadow-root/renderInShadowRoot'
import { startWatch } from '../../../utils/watcher'
import { postEditorInPopupSelector, postEditorInTimelineSelector } from '../utils/selector'
@@ -45,7 +45,7 @@ function PostDialogHintAtMinds({ reason }: { reason: 'timeline' | 'popup' }) {
const { classes } = useStyles({ reason })
const onHintButtonClicked = useCallback(
- () => MaskMessages.events.requestComposition.sendToLocal({ reason, open: true }),
+ () => CrossIsolationMessages.events.requestComposition.sendToLocal({ reason, open: true }),
[reason],
)
return (
diff --git a/packages/mask/src/social-network-adaptor/minds.com/shared.ts b/packages/mask/src/social-network-adaptor/minds.com/shared.ts
index bbc72b7a3382..ab4e841712e5 100644
--- a/packages/mask/src/social-network-adaptor/minds.com/shared.ts
+++ b/packages/mask/src/social-network-adaptor/minds.com/shared.ts
@@ -1,4 +1,6 @@
import type { PostIdentifier } from '@masknet/shared-base'
+import { openWindow } from '@masknet/shared-base-ui'
+import urlcat from 'urlcat'
import type { SocialNetwork } from '../../social-network/types'
import { createSNSAdaptorSpecializedPostContext } from '../../social-network/utils/create-post-context'
import { deconstructPayload } from '../../utils'
@@ -17,11 +19,13 @@ export const mindsShared: SocialNetwork.Shared & SocialNetwork.Base = {
textPayloadPostProcessor: undefined,
getPostURL,
share(message) {
- const url = this.getShareLinkURL!(message)
- window.open(url, '_blank', 'noopener noreferrer')
+ openWindow(this.getShareLinkURL?.(message))
},
getShareLinkURL(message) {
- return new URL(`https://www.minds.com/newsfeed/subscriptions?intentUrl=${encodeURIComponent(message)}`)
+ const url = urlcat('https://www.minds.com/newsfeed/subscriptions', {
+ intentUrl: message,
+ })
+ return new URL(url)
},
createPostContext: createSNSAdaptorSpecializedPostContext({
payloadParser: deconstructPayload,
diff --git a/packages/mask/src/social-network-adaptor/minds.com/ui-provider.ts b/packages/mask/src/social-network-adaptor/minds.com/ui-provider.ts
index db2f22d589ae..1c0bbfb1941d 100644
--- a/packages/mask/src/social-network-adaptor/minds.com/ui-provider.ts
+++ b/packages/mask/src/social-network-adaptor/minds.com/ui-provider.ts
@@ -125,10 +125,12 @@ const mindsUI: SocialNetworkUI.Definition = {
},
customization: {
paletteMode: PaletteModeProviderMinds,
- componentOverwrite: {
+ sharedComponentOverwrite: {
InjectedDialog: {
classes: useInjectedDialogClassesOverwriteMinds,
},
+ },
+ componentOverwrite: {
RenderFragments: MindsRenderFragments,
},
useTheme: useThemeMindsVariant,
diff --git a/packages/mask/src/social-network-adaptor/opensea.io/base.ts b/packages/mask/src/social-network-adaptor/opensea.io/base.ts
index 6757cb5464da..4e32a6a4a87b 100644
--- a/packages/mask/src/social-network-adaptor/opensea.io/base.ts
+++ b/packages/mask/src/social-network-adaptor/opensea.io/base.ts
@@ -1,7 +1,7 @@
+import { OPENSEA_ID } from '@masknet/shared'
import { SocialNetworkEnum } from '@masknet/encryption'
import type { SocialNetwork, SocialNetworkWorker } from '../../social-network/types'
-const OPENSEA_ID = 'opensea.io'
const origins = ['https://opensea.io/*']
export const openseaBase: SocialNetwork.Base = {
networkIdentifier: OPENSEA_ID,
diff --git a/packages/mask/src/social-network-adaptor/twitter.com/automation/openComposeBox.ts b/packages/mask/src/social-network-adaptor/twitter.com/automation/openComposeBox.ts
index a40eda021270..8312260fc5d5 100644
--- a/packages/mask/src/social-network-adaptor/twitter.com/automation/openComposeBox.ts
+++ b/packages/mask/src/social-network-adaptor/twitter.com/automation/openComposeBox.ts
@@ -1,11 +1,11 @@
-import { MaskMessages, CompositionRequest } from '../../../utils/messages'
+import { CrossIsolationMessages, CompositionRequest } from '@masknet/shared-base'
import { makeTypedMessageText, SerializableTypedMessages } from '@masknet/typed-message'
export function openComposeBoxTwitter(
content: string | SerializableTypedMessages,
options?: CompositionRequest['options'],
) {
- MaskMessages.events.requestComposition.sendToLocal({
+ CrossIsolationMessages.events.requestComposition.sendToLocal({
reason: 'timeline',
open: true,
content: typeof content === 'string' ? makeTypedMessageText(content) : content,
diff --git a/packages/mask/src/social-network-adaptor/twitter.com/base.ts b/packages/mask/src/social-network-adaptor/twitter.com/base.ts
index 6f6a912ac250..a90412694e6f 100644
--- a/packages/mask/src/social-network-adaptor/twitter.com/base.ts
+++ b/packages/mask/src/social-network-adaptor/twitter.com/base.ts
@@ -1,10 +1,10 @@
+import { TWITTER_ID } from '@masknet/shared'
import { SocialNetworkEnum } from '@masknet/encryption'
import type { SocialNetwork, SocialNetworkWorker } from '../../social-network/types'
-const id = 'twitter.com'
const origins = ['https://mobile.twitter.com/*', 'https://twitter.com/*']
export const twitterBase: SocialNetwork.Base = {
- networkIdentifier: id,
+ networkIdentifier: TWITTER_ID,
encryptionNetwork: SocialNetworkEnum.Twitter,
declarativePermissions: { origins },
shouldActivate(location) {
@@ -13,7 +13,7 @@ export const twitterBase: SocialNetwork.Base = {
}
export function isTwitter(ui: SocialNetwork.Base) {
- return ui.networkIdentifier === id
+ return ui.networkIdentifier === TWITTER_ID
}
export const twitterWorkerBase: SocialNetworkWorker.WorkerBase & SocialNetwork.Base = {
diff --git a/packages/mask/src/social-network-adaptor/twitter.com/collecting/identity.ts b/packages/mask/src/social-network-adaptor/twitter.com/collecting/identity.ts
index 77cab412d085..906d2ce076c3 100644
--- a/packages/mask/src/social-network-adaptor/twitter.com/collecting/identity.ts
+++ b/packages/mask/src/social-network-adaptor/twitter.com/collecting/identity.ts
@@ -5,7 +5,7 @@ import { ProfileIdentifier } from '@masknet/shared-base'
import { creator, SocialNetworkUI as Next } from '../../../social-network'
import Services from '../../../extension/service'
import { twitterBase } from '../base'
-import { getAvatar, getBioDescription, getNickname, getTwitterId, getPersonalHomepage } from '../utils/user'
+import { getAvatar, getBio, getNickname, getTwitterId, getPersonalHomepage } from '../utils/user'
import { delay } from '@dimensiondev/kit'
function resolveLastRecognizedIdentityInner(
@@ -43,7 +43,7 @@ function resolveCurrentVisitingIdentityInner(
const avatarMetaSelector = searchAvatarMetaSelector()
const assign = async () => {
await delay(500)
- const bio = getBioDescription()
+ const bio = getBio()
const homepage = getPersonalHomepage()
const nickname = getNickname()
const handle = getTwitterId()
diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarEditProfile.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarEditProfile.tsx
index 086e8df6b830..6f701edbf04b 100644
--- a/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarEditProfile.tsx
+++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarEditProfile.tsx
@@ -2,8 +2,7 @@ import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit'
import { makeStyles } from '@masknet/theme'
import { useState, useEffect } from 'react'
import { NFTAvatarButton } from '../../../../plugins/Avatar/SNSAdaptor/NFTAvatarButton'
-import { startWatch, createReactRootShadowed } from '../../../../utils'
-import { useLocationChange } from '../../../../utils/hooks/useLocationChange'
+import { startWatch, createReactRootShadowed, useLocationChange } from '../../../../utils'
import { searchEditProfileSelector } from '../../utils/selector'
export function injectOpenNFTAvatarEditProfileButton(signal: AbortSignal) {
diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarInTwitter.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarInTwitter.tsx
index debe79e92c43..530a742310ca 100644
--- a/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarInTwitter.tsx
+++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarInTwitter.tsx
@@ -15,6 +15,7 @@ import { useAsync, useLocation, useUpdateEffect, useWindowSize } from 'react-use
import { rainbowBorderKeyFrames } from '../../../../plugins/Avatar/SNSAdaptor/RainbowBox'
import { trim } from 'lodash-unified'
import { RSS3_KEY_SNS } from '../../../../plugins/Avatar/constants'
+import { openWindow } from '@masknet/shared-base-ui'
export function injectNFTAvatarInTwitter(signal: AbortSignal) {
const watcher = new MutationObserverWatcher(searchTwitterAvatarSelector())
@@ -194,7 +195,7 @@ function NFTAvatarInTwitter() {
if (!avatar || !linkParentDom || !showAvatar) return
const handler = () => {
- window.open(resolveOpenSeaLink(avatar.address, avatar.tokenId), '_blank')
+ openWindow(resolveOpenSeaLink(avatar.address, avatar.tokenId))
}
linkParentDom.addEventListener('click', handler)
diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostActions/index.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostActions/index.tsx
index b9d9eb617f35..637903840779 100644
--- a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostActions/index.tsx
+++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostActions/index.tsx
@@ -2,8 +2,8 @@ import type { PostInfo } from '@masknet/plugin-infra'
import { Flags } from '../../../../../shared'
import { createPostActionsInjector } from '../../../../social-network/defaults/inject/PostActions'
-export function injectPostActionsAtTwitter(signal: AbortSignal, current: PostInfo) {
+export function injectPostActionsAtTwitter(signal: AbortSignal, postInfo: PostInfo) {
if (!Flags.post_actions_enabled) return
const injector = createPostActionsInjector()
- return injector(current, signal)
+ return injector(postInfo, signal)
}
diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx
index 2eb6d896327d..5fc8d4baed8d 100644
--- a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx
+++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx
@@ -1,9 +1,9 @@
import { useCallback, useEffect, useState } from 'react'
import { MutationObserverWatcher, LiveSelector } from '@dimensiondev/holoflows-kit'
+import { CrossIsolationMessages } from '@masknet/shared-base'
import { isReplyPageSelector, postEditorInPopupSelector, searchReplyToolbarSelector } from '../utils/selector'
import { createReactRootShadowed } from '../../../utils/shadow-root/renderInShadowRoot'
import { PostDialogHint } from '../../../components/InjectedComponents/PostDialogHint'
-import { MaskMessages } from '../../../utils/messages'
import { startWatch } from '../../../utils/watcher'
import { makeStyles, MaskColorVar } from '@masknet/theme'
import { alpha } from '@mui/material'
@@ -61,7 +61,7 @@ function PostDialogHintAtTwitter({ reason }: { reason: 'timeline' | 'popup' }) {
t('setup_guide_say_hello_follow', { account: '@realMaskNetwork' }),
)
- MaskMessages.events.requestComposition.sendToLocal({
+ CrossIsolationMessages.events.requestComposition.sendToLocal({
reason: isReplyPageSelector() ? 'reply' : reason,
open: true,
content,
diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/Tip/ProfileTipButton.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/Tip/ProfileTipButton.tsx
index 2329b7d9ec1d..b23b444d28a9 100644
--- a/packages/mask/src/social-network-adaptor/twitter.com/injection/Tip/ProfileTipButton.tsx
+++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/Tip/ProfileTipButton.tsx
@@ -3,8 +3,7 @@ import { makeStyles } from '@masknet/theme'
import { useEffect, useState } from 'react'
import { useCurrentVisitingIdentity } from '../../../../components/DataSource/useActivatedUI'
import { TipButton } from '../../../../plugins/NextID/components/Tip/TipButton'
-import { createReactRootShadowed, startWatch } from '../../../../utils'
-import { useLocationChange } from '../../../../utils/hooks/useLocationChange'
+import { createReactRootShadowed, startWatch, useLocationChange } from '../../../../utils'
import {
profileFollowButtonSelector as selector,
profileMenuButtonSelector as menuButtonSelector,
@@ -17,14 +16,14 @@ export function injectOpenTipButtonOnProfile(signal: AbortSignal) {
}
interface StyleProps {
- minHeight: number
+ size: number
fontSize: number
marginBottom: number
}
const useStyles = makeStyles()((theme, props) => ({
button: {
- height: 34,
- width: 34,
+ height: props.size,
+ width: props.size,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
@@ -47,7 +46,7 @@ const useStyles = makeStyles()((theme, props) => ({
}))
function OpenTipDialog() {
- const [style, setStyle] = useState({ minHeight: 32, fontSize: 14, marginBottom: 11 })
+ const [style, setStyle] = useState({ size: 34, fontSize: 14, marginBottom: 11 })
const visitingPersona = useCurrentVisitingIdentity()
const setStyleFromEditProfileSelector = () => {
@@ -55,7 +54,7 @@ function OpenTipDialog() {
if (!menuButton) return
const css = window.getComputedStyle(menuButton)
setStyle({
- minHeight: Number.parseFloat(css.minHeight.replace('px', '')),
+ size: Number.parseFloat(css.height.replace('px', '')),
fontSize: Number.parseFloat(css.fontSize.replace('px', '')),
marginBottom: Number.parseFloat(css.marginBottom.replace('px', '')),
})
diff --git a/packages/mask/src/social-network-adaptor/twitter.com/shared.ts b/packages/mask/src/social-network-adaptor/twitter.com/shared.ts
index de652947450b..f971f7549c1b 100644
--- a/packages/mask/src/social-network-adaptor/twitter.com/shared.ts
+++ b/packages/mask/src/social-network-adaptor/twitter.com/shared.ts
@@ -1,8 +1,9 @@
import { PostIdentifier, ProfileIdentifier } from '@masknet/shared-base'
+import { openWindow } from '@masknet/shared-base-ui'
+import urlcat from 'urlcat'
import type { SocialNetwork } from '../../social-network/types'
import { createSNSAdaptorSpecializedPostContext } from '../../social-network/utils/create-post-context'
import { deconstructPayload } from '../../utils'
-import { createCenterWindowConfig } from '../utils'
import { twitterBase } from './base'
import { twitterEncoding } from './encoding'
import { usernameValidator } from './utils/user'
@@ -27,12 +28,30 @@ export const twitterShared: SocialNetwork.Shared & SocialNetwork.Base = {
},
getPostURL,
share(text) {
- const config = createCenterWindowConfig(700, 520)
const url = this.getShareLinkURL!(text)
- window.open(url, 'share', config) || window.location.assign(url)
+ const width = 700
+ const height = 520
+ const openedWindow = openWindow(url, 'share', {
+ width,
+ height,
+ screenX: window.screenX + (window.innerWidth - width) / 2,
+ screenY: window.screenY + (window.innerHeight - height) / 2,
+ opener: true,
+ referrer: true,
+ behaviors: {
+ toolbar: true,
+ status: true,
+ resizable: true,
+ scrollbars: true,
+ },
+ })
+ if (openedWindow === null) {
+ location.assign(url)
+ }
},
getShareLinkURL(message) {
- return new URL(`https://twitter.com/intent/tweet?text=${encodeURIComponent(message)}`)
+ const url = urlcat('https://twitter.com/intent/tweet', { text: message })
+ return new URL(url)
},
createPostContext: createSNSAdaptorSpecializedPostContext({
payloadParser: deconstructPayload,
diff --git a/packages/mask/src/social-network-adaptor/twitter.com/ui-provider.ts b/packages/mask/src/social-network-adaptor/twitter.com/ui-provider.ts
index ad5d4536c559..872aed50431c 100644
--- a/packages/mask/src/social-network-adaptor/twitter.com/ui-provider.ts
+++ b/packages/mask/src/social-network-adaptor/twitter.com/ui-provider.ts
@@ -134,10 +134,12 @@ const twitterUI: SocialNetworkUI.Definition = {
},
customization: {
paletteMode: PaletteModeProviderTwitter,
- componentOverwrite: {
+ sharedComponentOverwrite: {
InjectedDialog: {
classes: useInjectedDialogClassesOverwriteTwitter,
},
+ },
+ componentOverwrite: {
RenderFragments: TwitterRenderFragments,
},
useTheme: useThemeTwitterVariant,
diff --git a/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts b/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts
index 398d94890015..fb567de08e29 100644
--- a/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts
+++ b/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts
@@ -57,7 +57,7 @@ export const searchNewTweetButtonSelector: () => LiveSelector = () => {
}
export const searchNickNameSelector: () => LiveSelector = () =>
- querySelector('[data-testid="tweet"] a:not([target]) > div > div[dir="auto"] > span > span')
+ querySelector('[data-testid="primaryColumn"] [data-testid="UserName"] div[dir="auto"] > span > span')
export const searchAvatarSelector = () =>
querySelector('[data-testid="primaryColumn"] a[href$="/photo"] img[src*="profile_images"]')
export const searchNFTAvatarSelector = () =>
@@ -146,7 +146,7 @@ export const twitterMainAvatarSelector: () => LiveSelector = () =>
export const newPostButtonSelector = () => querySelector('[data-testid="SideNav_NewTweet_Button"]')
-export const bioDescriptionSelector = () => querySelector('[data-testid="UserDescription"]')
+export const profileBioSelector = () => querySelector('[data-testid="UserDescription"]')
export const personalHomepageSelector = () => querySelector('[data-testid="UserUrl"]')
diff --git a/packages/mask/src/social-network-adaptor/twitter.com/utils/user.ts b/packages/mask/src/social-network-adaptor/twitter.com/utils/user.ts
index 1fab6ced45c1..d0f6a1f62a6e 100644
--- a/packages/mask/src/social-network-adaptor/twitter.com/utils/user.ts
+++ b/packages/mask/src/social-network-adaptor/twitter.com/utils/user.ts
@@ -1,7 +1,7 @@
import { isNull } from 'lodash-unified'
import type { SocialNetwork } from '../../../social-network'
import {
- bioDescriptionSelector,
+ profileBioSelector,
searchAvatarSelector,
searchNickNameSelector,
personalHomepageSelector,
@@ -23,7 +23,7 @@ export const usernameValidator: NonNullable {
- const node = searchNickNameSelector().evaluate()?.querySelector('span span')
+ const node = searchNickNameSelector().evaluate()
if (!node) return ''
return collectNodeText(node)
@@ -42,8 +42,8 @@ export const getTwitterId = () => {
return ''
}
-export const getBioDescription = () => {
- const node = bioDescriptionSelector().evaluate()
+export const getBio = () => {
+ const node = profileBioSelector().evaluate()
return node ? collectNodeText(node) : ''
}
diff --git a/packages/mask/src/social-network-adaptor/utils.ts b/packages/mask/src/social-network-adaptor/utils.ts
index a0e6273f3514..1349c4980437 100644
--- a/packages/mask/src/social-network-adaptor/utils.ts
+++ b/packages/mask/src/social-network-adaptor/utils.ts
@@ -23,18 +23,3 @@ export const getCurrentIdentifier = () => {
globalUIState.profiles.value[0]
)
}
-
-export const createCenterWindowConfig = (width: number, height: number) => {
- const x = window.screenX + (window.innerWidth - width) / 2
- const y = window.screenY + (window.innerHeight - height) / 2
- return [
- `screenX=${x}`,
- `screenY=${y}`,
- 'toolbar=1',
- 'status=1',
- 'resizable=1',
- 'scrollbars=1',
- `height=${height}`,
- `width=${width}`,
- ].join(',')
-}
diff --git a/packages/mask/src/social-network/types.ts b/packages/mask/src/social-network/types.ts
index 09ea52e4fb6b..5601bec36c8b 100644
--- a/packages/mask/src/social-network/types.ts
+++ b/packages/mask/src/social-network/types.ts
@@ -11,13 +11,12 @@ import type {
} from '@masknet/shared-base'
import type { SerializableTypedMessages } from '@masknet/typed-message'
import type { RenderFragmentsContextType } from '@masknet/typed-message/dom'
+import type { SharedComponentOverwrite } from '@masknet/shared'
import type { PaletteMode, Theme } from '@mui/material'
import type { Subscription } from 'use-subscription'
-import type { InjectedDialogClassKey, InjectedDialogProps } from '../components/shared/InjectedDialog'
import type { Profile } from '../database'
import type { createSNSAdaptorSpecializedPostContext } from './utils/create-post-context'
-type ClassNameMap = { [P in ClassKey]: string }
// Don't define values in namespaces
export namespace SocialNetwork {
export interface PayloadEncoding {
@@ -270,6 +269,7 @@ export namespace SocialNetworkUI {
/** Provide the ability to detect the current color scheme (light or dark) in the current SNS */
paletteMode?: PaletteModeProvider
i18nOverwrite?: I18NOverwrite
+ sharedComponentOverwrite?: SharedComponentOverwrite
componentOverwrite?: ComponentOverwrite
}
export interface PaletteModeProvider {
@@ -277,13 +277,8 @@ export namespace SocialNetworkUI {
start(signal: AbortSignal): void
}
export interface ComponentOverwrite {
- InjectedDialog?: ComponentOverwriteConfig
RenderFragments?: RenderFragmentsContextType
}
- export interface ComponentOverwriteConfig {
- classes?: () => { classes: Partial> }
- props?: (props: Props) => Props
- }
export interface I18NOverwrite {
[namespace: string]: I18NOverwriteNamespace
}
diff --git a/packages/mask/src/social-network/ui.ts b/packages/mask/src/social-network/ui.ts
index a83e52c5f38d..4c29d701ee16 100644
--- a/packages/mask/src/social-network/ui.ts
+++ b/packages/mask/src/social-network/ui.ts
@@ -21,6 +21,7 @@ import { createPluginHost } from '../plugin-infra/host'
import { definedSocialNetworkUIs } from './define'
import { setupShadowRootPortal, MaskMessages } from '../utils'
import { delay, waitDocumentReadyState } from '@dimensiondev/kit'
+import { sharedUINetworkIdentifier, sharedUIComponentOverwrite } from '@masknet/shared'
import { SocialNetworkEnum } from '@masknet/encryption'
const definedSocialNetworkUIsResolved = new Map()
@@ -51,6 +52,12 @@ export async function activateSocialNetworkUIInner(ui_deferred: SocialNetworkUI.
console.log('Activating provider', ui_deferred.networkIdentifier)
const ui = (activatedSocialNetworkUI = await loadSocialNetworkUI(ui_deferred.networkIdentifier))
+
+ sharedUINetworkIdentifier.value = ui_deferred.networkIdentifier
+ if (ui.customization.sharedComponentOverwrite) {
+ sharedUIComponentOverwrite.value = ui.customization.sharedComponentOverwrite
+ }
+
console.log('Provider activated. You can access it by globalThis.ui', ui)
Object.assign(globalThis, { ui })
diff --git a/packages/mask/src/social-network/utils/create-post-context.ts b/packages/mask/src/social-network/utils/create-post-context.ts
index 8e6561790e5f..bc1c175702ff 100644
--- a/packages/mask/src/social-network/utils/create-post-context.ts
+++ b/packages/mask/src/social-network/utils/create-post-context.ts
@@ -18,10 +18,11 @@ import {
SubscriptionFromValueRef,
SubscriptionDebug as debug,
mapSubscription,
+ EMPTY_LIST,
} from '@masknet/shared-base'
import { Err, Result } from 'ts-results'
import type { Subscription } from 'use-subscription'
-import { activatedSocialNetworkUI } from '../'
+import { activatedSocialNetworkUI } from '../ui'
import { resolveFacebookLink } from '../../social-network-adaptor/facebook.com/utils/resolveFacebookLink'
import type { SupportedPayloadVersions } from '@masknet/encryption'
@@ -60,7 +61,7 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct
}),
)
const linksSubscribe: Subscription = debug({
- getCurrentValue: () => [...links],
+ getCurrentValue: () => (links.size ? [...links] : EMPTY_LIST),
subscribe: (sub) => links.event.on(ALL_EVENTS, sub),
})
// #endregion
@@ -138,7 +139,7 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct
postMetadataImages:
opt.postImagesProvider ||
debug({
- getCurrentValue: () => [],
+ getCurrentValue: () => EMPTY_LIST,
subscribe: () => () => {},
}),
@@ -181,11 +182,12 @@ export function createRefsForCreatePostContext() {
snsID: SubscriptionFromValueRef(postID),
rawMessage: SubscriptionFromValueRef(postMessage),
postImagesProvider: debug({
- getCurrentValue: () => [...postMetadataImages],
+ getCurrentValue: () => (postMetadataImages.size ? [...postMetadataImages] : EMPTY_LIST),
subscribe: (sub) => postMetadataImages.event.on(ALL_EVENTS, sub),
}),
postMentionedLinksProvider: debug({
- getCurrentValue: () => [...postMetadataMentionedLinks.values()],
+ getCurrentValue: () =>
+ postMetadataMentionedLinks.size ? [...postMetadataMentionedLinks.values()] : EMPTY_LIST,
subscribe: (sub) => postMetadataMentionedLinks.event.on(ALL_EVENTS, sub),
}),
}
diff --git a/packages/mask/src/tsconfig.json b/packages/mask/src/tsconfig.json
index 40b24ac85751..eb5b6ba34de4 100644
--- a/packages/mask/src/tsconfig.json
+++ b/packages/mask/src/tsconfig.json
@@ -23,6 +23,7 @@
{ "path": "../../web3-shared/base" },
{ "path": "../../web3-shared/evm" },
{ "path": "../../web3-shared/flow" },
+ { "path": "../../web3-shared/solana" },
{ "path": "../../web3-providers" },
{ "path": "../../theme" },
{ "path": "../../icons" },
diff --git a/packages/mask/src/utils/hooks/index.ts b/packages/mask/src/utils/hooks/index.ts
index 5d302e13ece0..924c4887e5d5 100644
--- a/packages/mask/src/utils/hooks/index.ts
+++ b/packages/mask/src/utils/hooks/index.ts
@@ -4,3 +4,4 @@ export * from './useMenu'
export * from './useQueryNavigatorPermission'
export * from './useSettingSwitcher'
export * from './useSuspense'
+export * from './useLocationChange'
diff --git a/packages/mask/src/utils/shadow-root/index.ts b/packages/mask/src/utils/shadow-root/index.ts
index b7f722bb05a6..046fdafecb69 100644
--- a/packages/mask/src/utils/shadow-root/index.ts
+++ b/packages/mask/src/utils/shadow-root/index.ts
@@ -1,6 +1 @@
export * from './renderInShadowRoot'
-export {
- usePortalShadowRoot,
- createShadowRootForwardedComponent,
- createShadowRootForwardedPopperComponent,
-} from '@masknet/theme'
diff --git a/packages/mask/src/web3/UI/EthereumChainBoundary.tsx b/packages/mask/src/web3/UI/EthereumChainBoundary.tsx
index 91ed0bb4a016..ae7288a76a70 100644
--- a/packages/mask/src/web3/UI/EthereumChainBoundary.tsx
+++ b/packages/mask/src/web3/UI/EthereumChainBoundary.tsx
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react'
import { Box, Typography, Theme } from '@mui/material'
import { makeStyles, useStylesExtends } from '@masknet/theme'
import type { SxProps } from '@mui/system'
-import { NetworkPluginID, useActivatedPlugin, usePluginIDContext, useAccount } from '@masknet/plugin-infra'
+import { NetworkPluginID, useActivatedPlugin, useCurrentWeb3NetworkPluginID, useAccount } from '@masknet/plugin-infra'
import {
ChainId,
getChainDetailedCAIP,
@@ -45,7 +45,7 @@ export interface EthereumChainBoundaryProps extends withClasses<'switchButton'>
export function EthereumChainBoundary(props: EthereumChainBoundaryProps) {
const { t } = useI18N()
- const pluginID = usePluginIDContext()
+ const pluginID = useCurrentWeb3NetworkPluginID()
const plugin = useActivatedPlugin(pluginID, 'any')
const account = useAccount()
diff --git a/packages/mask/src/web3/UI/EthereumERC721TokenApprovedBoundary.tsx b/packages/mask/src/web3/UI/EthereumERC721TokenApprovedBoundary.tsx
index f30417c61a95..e10bd3c2760b 100644
--- a/packages/mask/src/web3/UI/EthereumERC721TokenApprovedBoundary.tsx
+++ b/packages/mask/src/web3/UI/EthereumERC721TokenApprovedBoundary.tsx
@@ -107,7 +107,13 @@ export function EthereumERC721TokenApprovedBoundary(props: EthereumERC712TokenAp
loading
disabled
{...props.ActionButtonProps}>
- {t('plugin_wallet_nft_approving_all', { symbol: contractDetailed?.symbol })}
+ {t('plugin_wallet_nft_approving_all', {
+ symbol: contractDetailed?.symbol
+ ? contractDetailed.symbol.toLowerCase() === 'unknown'
+ ? 'All'
+ : contractDetailed.symbol
+ : 'All',
+ })}
)
} else if (validationMessage) {
@@ -143,7 +149,13 @@ export function EthereumERC721TokenApprovedBoundary(props: EthereumERC712TokenAp
fullWidth
onClick={approveCallback}
{...props.ActionButtonProps}>
- {t('plugin_wallet_approve_all_nft', { symbol: contractDetailed?.symbol })}
+ {t('plugin_wallet_approve_all_nft', {
+ symbol: contractDetailed?.symbol
+ ? contractDetailed.symbol.toLowerCase() === 'unknown'
+ ? 'All'
+ : contractDetailed.symbol
+ : 'All',
+ })}
)
} else if (value === undefined) {
diff --git a/packages/mask/utils-pure/index.ts b/packages/mask/utils-pure/index.ts
index 6ba5881a1d04..3a1a43716896 100644
--- a/packages/mask/utils-pure/index.ts
+++ b/packages/mask/utils-pure/index.ts
@@ -1,5 +1,4 @@
export * from './type'
-export * from './misc'
export * from './hmr'
export * from './crypto'
export * from './OnDemandWorker'
diff --git a/packages/plugin-infra/src/hooks/index.ts b/packages/plugin-infra/src/hooks/index.ts
index e9272b78222b..9ebf21266102 100644
--- a/packages/plugin-infra/src/hooks/index.ts
+++ b/packages/plugin-infra/src/hooks/index.ts
@@ -2,6 +2,7 @@ export * from './useActivatedPlugin'
export * from './useActivatedPluginWeb3UI'
export * from './useActivatedPluginWeb3State'
export * from './useAllPluginsWeb3State'
+export * from './useAvailablePlugins'
export * from './useLookupDomain'
export * from './useReverseAddress'
export * from './useI18N'
diff --git a/packages/plugin-infra/src/hooks/useAvailablePlugins.ts b/packages/plugin-infra/src/hooks/useAvailablePlugins.ts
new file mode 100644
index 000000000000..46f3776e6c81
--- /dev/null
+++ b/packages/plugin-infra/src/hooks/useAvailablePlugins.ts
@@ -0,0 +1,21 @@
+import { useMemo } from 'react'
+import type { Plugin } from '../types'
+import { useChainId, useCurrentWeb3NetworkPluginID } from '../web3'
+import type { NetworkPluginID } from '../web3-types'
+
+type HasRequirement = { enableRequirement: Plugin.Shared.Definition['enableRequirement'] }
+
+function checkPluginAvailable(plugin: HasRequirement, pluginId: NetworkPluginID, chainId: number) {
+ const supportedChainIds = plugin.enableRequirement.web3?.[pluginId]?.supportedChainIds
+ if (!supportedChainIds) return true
+ return supportedChainIds.includes(chainId)
+}
+
+export function useAvailablePlugins(plugins: T[]) {
+ const networkPluginId = useCurrentWeb3NetworkPluginID()
+ const chainId = useChainId(networkPluginId)
+ return useMemo(
+ () => plugins.filter((plugin) => checkPluginAvailable(plugin, networkPluginId, chainId)),
+ [plugins, networkPluginId, chainId],
+ )
+}
diff --git a/packages/plugin-infra/src/types.ts b/packages/plugin-infra/src/types.ts
index 309b9b3c5af1..94a2be28412d 100644
--- a/packages/plugin-infra/src/types.ts
+++ b/packages/plugin-infra/src/types.ts
@@ -242,10 +242,8 @@ export namespace Plugin.SNSAdaptor {
CompositionDialogEntry?: CompositionDialogEntry
/** This UI will be use when there is known badges. */
CompositionDialogMetadataBadgeRender?: CompositionMetadataBadgeRender
- /** This UI will be rendered as an entry in the toolbar (if the SNS has a Toolbar support) */
- ToolbarEntry?: ToolbarEntry
/** This UI will be rendered as an entry in the wallet status dialog */
- ApplicationEntry?: ApplicationEntry
+ ApplicationEntries?: ApplicationEntry[]
/** This UI will be rendered as sliders on the profile page */
ProfileSliders?: ProfileSlider[]
/** This UI will be rendered as tabs on the profile page */
@@ -310,51 +308,15 @@ export namespace Plugin.SNSAdaptor {
}
// #endregion
- // #region Toolbar entry
- export interface ToolbarEntry {
- image: string
- // TODO: remove string
- label: I18NStringField | string
- /**
- * Used to order the toolbars
- *
- * TODO: can we make them unordered?
- */
- priority: number
- /**
- * This is a React hook. If it returns false, this entry will not be displayed.
- */
- useShouldDisplay?(): boolean
- /**
- * What to do if the entry is clicked.
- */
- // TODO: add support for DialogEntry.
- // TODO: add support for onClick event.
- onClick: 'openCompositionEntry'
- }
- // #endregion
-
export interface ApplicationEntry {
/**
- * The icon image URL
- */
- icon: URL
- /**
- * The name of the application
- */
- label: I18NStringField | string
- /**
- * Also an entrance in a sub-folder
+ * Render entry component
*/
- categoryID?: string
+ RenderEntryComponent: (props: { disabled: boolean }) => JSX.Element
/**
* Used to order the applications on the board
*/
- priority: number
- /**
- * What to do if the application icon is clicked.
- */
- onClick(): void
+ defaultSortingPriority: number
}
export interface ProfileIdentity {
@@ -726,6 +688,8 @@ export enum PluginId {
UnlockProtocol = 'com.maskbook.unlockprotocol',
FileService = 'com.maskbook.fileservice',
CyberConnect = 'me.cyberconnect.app',
+ GoPlusSecurity = 'io.gopluslabs.security',
+ CrossChainBridge = 'io.mask.cross-chain-bridge',
// @masknet/scripts: insert-here
}
diff --git a/packages/plugin-infra/src/utils/createInjectHooksRenderer.tsx b/packages/plugin-infra/src/utils/createInjectHooksRenderer.tsx
index 2da5547361b3..1e68250fbe85 100644
--- a/packages/plugin-infra/src/utils/createInjectHooksRenderer.tsx
+++ b/packages/plugin-infra/src/utils/createInjectHooksRenderer.tsx
@@ -1,9 +1,9 @@
-import { useEffect, useState, useRef, memo, createContext, useContext } from 'react'
import { ErrorBoundary } from '@masknet/shared-base-ui'
import { ShadowRootIsolation } from '@masknet/theme'
-import type { Plugin } from '../types'
-import { usePluginI18NField, PluginWrapperComponent, PluginWrapperMethods } from '../hooks'
+import { createContext, memo, useContext, useEffect, useRef, useState } from 'react'
+import { PluginWrapperComponent, PluginWrapperMethods, useAvailablePlugins, usePluginI18NField } from '../hooks'
import { PluginWrapperMethodsContext } from '../hooks/usePluginWrapper'
+import type { Plugin } from '../types'
type Inject = Plugin.InjectUI
type Raw = Plugin.InjectUIRaw
@@ -43,15 +43,15 @@ export function createInjectHooksRenderer (
-
-
-
-
-
- ))
+ const allPlugins = usePlugins()
+ const availablePlugins = useAvailablePlugins(allPlugins) as PluginDefinition[]
+ const all = availablePlugins.filter(pickInjectorHook).map((plugin) => (
+
+
+
+
+
+ ))
return <>{all}>
}
return memo(function PluginsInjectionHookRenderErrorBoundary(props: PropsType) {
diff --git a/packages/plugin-infra/src/web3-types.ts b/packages/plugin-infra/src/web3-types.ts
index d851716caf51..5c1442031d6b 100644
--- a/packages/plugin-infra/src/web3-types.ts
+++ b/packages/plugin-infra/src/web3-types.ts
@@ -29,6 +29,18 @@ export type Color =
| `#${string}${string}${string}`
| `hsl(${number}, ${number}%, ${number}%)`
+// Borrow from @masknet/web3-shared-evm
+interface ERC721TokenInfo {
+ name?: string
+ description?: string
+ tokenURI?: string
+ mediaUrl?: string
+ imageURL?: string
+ owner?: string
+ // loading tokenURI
+ hasTokenDetailed?: boolean
+}
+
export declare namespace Web3Plugin {
/**
* Plugin can declare what chain it supports to trigger side effects (e.g. create a new transaction).
@@ -197,6 +209,7 @@ export declare namespace Web3Plugin {
description?: string
owner?: string
metadata?: NonFungibleTokenMetadata
+ info?: ERC721TokenInfo
contract?: NonFungibleContract
}
diff --git a/packages/plugin-infra/src/web3/Context.tsx b/packages/plugin-infra/src/web3/Context.tsx
index 0e2fb578cee0..be8fd9e12190 100644
--- a/packages/plugin-infra/src/web3/Context.tsx
+++ b/packages/plugin-infra/src/web3/Context.tsx
@@ -78,7 +78,7 @@ function usePluginsWeb3State() {
)
}
-export function usePluginIDContext() {
+export function useCurrentWeb3NetworkPluginID() {
return useContext(PluginIDContext)
}
@@ -87,7 +87,7 @@ const PluginsWeb3StateContext = createContainer(usePluginsWeb3State)
export const usePluginsWeb3StateContext = PluginsWeb3StateContext.useContainer
export function usePluginWeb3StateContext(expectedPluginID?: NetworkPluginID) {
- const pluginID = usePluginIDContext()
+ const pluginID = useCurrentWeb3NetworkPluginID()
const pluginsWeb3State = usePluginsWeb3StateContext()
return pluginsWeb3State[expectedPluginID ?? pluginID] ?? {}
}
diff --git a/packages/plugin-infra/src/web3/useNetworkDescriptor.ts b/packages/plugin-infra/src/web3/useNetworkDescriptor.ts
index 5f906dc28ba2..f7bb10353f38 100644
--- a/packages/plugin-infra/src/web3/useNetworkDescriptor.ts
+++ b/packages/plugin-infra/src/web3/useNetworkDescriptor.ts
@@ -1,13 +1,13 @@
import type { NetworkPluginID } from '..'
import { useNetworkType } from './useNetworkType'
-import { usePluginIDContext } from './Context'
+import { useCurrentWeb3NetworkPluginID } from './Context'
import { getPluginDefine } from '../manager/store'
export function useNetworkDescriptor(
expectedChainIdOrNetworkTypeOrID?: number | string,
expectedPluginID?: NetworkPluginID,
) {
- const pluginID = usePluginIDContext()
+ const pluginID = useCurrentWeb3NetworkPluginID()
const pid = expectedPluginID ?? pluginID
const networkType = useNetworkType(pid)
diff --git a/packages/plugin-infra/src/web3/useNetworkDescriptors.ts b/packages/plugin-infra/src/web3/useNetworkDescriptors.ts
index bc344101ae5c..162d5fe303e7 100644
--- a/packages/plugin-infra/src/web3/useNetworkDescriptors.ts
+++ b/packages/plugin-infra/src/web3/useNetworkDescriptors.ts
@@ -1,7 +1,7 @@
-import { usePluginIDContext } from '.'
+import { useCurrentWeb3NetworkPluginID } from '.'
import { getPluginDefine } from '..'
export function useNetworkDescriptors(expectedPluginID?: string) {
- const pluginID = usePluginIDContext()
+ const pluginID = useCurrentWeb3NetworkPluginID()
return getPluginDefine(expectedPluginID ?? pluginID)?.declareWeb3Networks ?? []
}
diff --git a/packages/plugin-infra/src/web3/useProviderDescriptor.ts b/packages/plugin-infra/src/web3/useProviderDescriptor.ts
index 76f40c997d06..3b48bd3b49d7 100644
--- a/packages/plugin-infra/src/web3/useProviderDescriptor.ts
+++ b/packages/plugin-infra/src/web3/useProviderDescriptor.ts
@@ -1,10 +1,10 @@
import type { NetworkPluginID } from '..'
import { useProviderType } from './useProviderType'
-import { usePluginIDContext } from './Context'
+import { useCurrentWeb3NetworkPluginID } from './Context'
import { getPluginDefine } from '../manager/store'
export function useProviderDescriptor(expectedProviderTypeOrID?: string, expectedPluginID?: NetworkPluginID) {
- const pluginID = usePluginIDContext()
+ const pluginID = useCurrentWeb3NetworkPluginID()
const providerType = useProviderType(expectedPluginID ?? pluginID)
return getPluginDefine(expectedPluginID ?? pluginID)?.declareWeb3Providers?.find((x) =>
diff --git a/packages/plugin-infra/src/web3/useProviderDescriptors.ts b/packages/plugin-infra/src/web3/useProviderDescriptors.ts
index 352b3c3116a0..0db2b0167c14 100644
--- a/packages/plugin-infra/src/web3/useProviderDescriptors.ts
+++ b/packages/plugin-infra/src/web3/useProviderDescriptors.ts
@@ -1,7 +1,7 @@
-import { usePluginIDContext } from '.'
+import { useCurrentWeb3NetworkPluginID } from '.'
import { getPluginDefine } from '..'
export function useProviderDescriptors(expectedPluginID?: string) {
- const pluginID = usePluginIDContext()
+ const pluginID = useCurrentWeb3NetworkPluginID()
return getPluginDefine(expectedPluginID ?? pluginID)?.declareWeb3Providers ?? []
}
diff --git a/packages/plugin-infra/src/web3/useWeb3State.ts b/packages/plugin-infra/src/web3/useWeb3State.ts
index 07689afb6548..381cb694a293 100644
--- a/packages/plugin-infra/src/web3/useWeb3State.ts
+++ b/packages/plugin-infra/src/web3/useWeb3State.ts
@@ -1,8 +1,8 @@
-import { usePluginIDContext } from './Context'
+import { useCurrentWeb3NetworkPluginID } from './Context'
import { useActivatedPluginWeb3State } from '../hooks/useActivatedPluginWeb3State'
import type { NetworkPluginID } from '..'
export function useWeb3State(expectedPluginID?: NetworkPluginID) {
- const pluginID = usePluginIDContext()
+ const pluginID = useCurrentWeb3NetworkPluginID()
return useActivatedPluginWeb3State(expectedPluginID ?? pluginID) ?? {}
}
diff --git a/packages/plugin-infra/src/web3/useWeb3UI.ts b/packages/plugin-infra/src/web3/useWeb3UI.ts
index c30e3eb98baa..4a23eb31c66d 100644
--- a/packages/plugin-infra/src/web3/useWeb3UI.ts
+++ b/packages/plugin-infra/src/web3/useWeb3UI.ts
@@ -1,8 +1,8 @@
-import { usePluginIDContext } from './Context'
+import { useCurrentWeb3NetworkPluginID } from './Context'
import { useActivatedPluginWeb3UI } from '../hooks/useActivatedPluginWeb3UI'
import type { NetworkPluginID } from '..'
export function useWeb3UI(expectedPluginID?: NetworkPluginID) {
- const pluginID = usePluginIDContext()
+ const pluginID = useCurrentWeb3NetworkPluginID()
return useActivatedPluginWeb3UI(expectedPluginID ?? pluginID) ?? {}
}
diff --git a/packages/plugins/CrossChainBridge/README.md b/packages/plugins/CrossChainBridge/README.md
new file mode 100644
index 000000000000..369c4cfa282c
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/README.md
@@ -0,0 +1,5 @@
+# Cross-chain bridge plugin
+
+## TODOs
+
+add more cross-chain bridge website
diff --git a/packages/plugins/CrossChainBridge/package.json b/packages/plugins/CrossChainBridge/package.json
new file mode 100644
index 000000000000..d4499c87bdbd
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@masknet/plugin-cross-chain-bridge",
+ "private": true,
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "dependencies": {
+ "@masknet/plugin-infra": "workspace:*",
+ "@masknet/shared": "workspace:*",
+ "@masknet/shared-base": "workspace:*",
+ "react-use": "^17.3.2"
+ }
+}
diff --git a/packages/plugins/CrossChainBridge/src/Dashboard/index.tsx b/packages/plugins/CrossChainBridge/src/Dashboard/index.tsx
new file mode 100644
index 000000000000..3052bb2f8ad3
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/Dashboard/index.tsx
@@ -0,0 +1,9 @@
+import type { Plugin } from '@masknet/plugin-infra'
+import { base } from '../base'
+
+const dashboard: Plugin.Dashboard.Definition = {
+ ...base,
+ init(signal, context) {},
+}
+
+export default dashboard
diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/CrossChainBridgeDialog.tsx b/packages/plugins/CrossChainBridge/src/SNSAdaptor/CrossChainBridgeDialog.tsx
new file mode 100644
index 000000000000..c7f75df14c97
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/SNSAdaptor/CrossChainBridgeDialog.tsx
@@ -0,0 +1,99 @@
+import { DialogContent, Stack, Typography } from '@mui/material'
+import { makeStyles, MaskDialog, useStylesExtends } from '@masknet/theme'
+import { useI18N } from '../locales'
+import { getCrossChainBridge } from '../constants'
+import { openWindow } from '@masknet/shared-base-ui'
+
+const useStyles = makeStyles()((theme) => ({
+ root: {
+ width: 600,
+ },
+ paperRoot: {
+ backgroundImage: 'none',
+ '&>h2': {
+ height: 30,
+ border: `1px solid ${theme.palette.divider}`,
+ padding: theme.spacing(1.875, 2.5, 1.875, 2.5),
+ marginBottom: 24,
+ },
+ },
+ content: {
+ width: 552,
+ maxHeight: 510,
+ paddingBottom: theme.spacing(3),
+ },
+ bridgeItem: {
+ display: 'flex',
+ padding: '12px',
+ ':hover': {
+ backgroundColor: theme.palette.background.default,
+ cursor: 'pointer',
+ },
+ },
+ bridgeInfo: {
+ marginLeft: '8px',
+ },
+ bridgeName: {
+ fontSize: '24px',
+ fontWeight: 600,
+ display: 'flex',
+ },
+ bridgeIntro: {
+ fontSize: '12px',
+ fontWeight: 400,
+ color: theme.palette.grey[700],
+ },
+ officialTag: {
+ width: '39px',
+ height: '15px',
+ fontSize: '12px',
+ fontWeight: 700,
+ alignSelf: 'center',
+ borderRadius: '8px',
+ backgroundColor: 'rgba(28, 104, 243, 0.1)',
+ color: '#1c68f3',
+ padding: '4px 12px 6px 8px',
+ marginLeft: '4px',
+ },
+}))
+
+export interface CrossChainBridgeDialogProps extends withClasses {
+ open: boolean
+ onClose(): void
+}
+
+export function CrossChainBridgeDialog(props: CrossChainBridgeDialogProps) {
+ const t = useI18N()
+ const classes = useStylesExtends(useStyles(), props)
+ const { open, onClose } = props
+ const bridges = getCrossChainBridge(t)
+
+ return (
+
+
+
+ {bridges?.map((bridge) => (
+ openWindow(bridge?.link)}>
+ {bridge?.icon}
+
+
+ {bridge?.name}
+ {bridge?.isOfficial && (
+ {t.official()}
+ )}
+
+ {bridge?.intro && (
+
{bridge.intro}
+ )}
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/MaskIcon.tsx b/packages/plugins/CrossChainBridge/src/SNSAdaptor/MaskIcon.tsx
new file mode 100644
index 000000000000..5bf482a61c53
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/SNSAdaptor/MaskIcon.tsx
@@ -0,0 +1,54 @@
+export function ArbitrumOneBridgeIcon({ size = 36 }) {
+ return (
+
+ )
+}
+
+export function BobaBridgeIcon({ size = 36 }) {
+ return (
+
+ )
+}
+
+export function CBridgeIcon({ size = 36 }) {
+ return (
+
+ )
+}
+
+export function PolygonBridgeIcon({ size = 36 }) {
+ return (
+
+ )
+}
+
+export function RainbowBridgeIcon({ size = 36 }) {
+ return (
+
+ )
+}
diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/arbitrum-one-bridge.png b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/arbitrum-one-bridge.png
new file mode 100644
index 000000000000..634ec832596d
Binary files /dev/null and b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/arbitrum-one-bridge.png differ
diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/boba-bridge.png b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/boba-bridge.png
new file mode 100644
index 000000000000..b41c5c9b9b2c
Binary files /dev/null and b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/boba-bridge.png differ
diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/cbridge.png b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/cbridge.png
new file mode 100644
index 000000000000..e9ced6d2c547
Binary files /dev/null and b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/cbridge.png differ
diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/cross-chain.png b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/cross-chain.png
new file mode 100644
index 000000000000..892dd5876ecb
Binary files /dev/null and b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/cross-chain.png differ
diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/polygon-bridge.png b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/polygon-bridge.png
new file mode 100644
index 000000000000..a8c63f65c0c4
Binary files /dev/null and b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/polygon-bridge.png differ
diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/rainbow-bridge.png b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/rainbow-bridge.png
new file mode 100644
index 000000000000..712f8e2b9a34
Binary files /dev/null and b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/rainbow-bridge.png differ
diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/index.tsx b/packages/plugins/CrossChainBridge/src/SNSAdaptor/index.tsx
new file mode 100644
index 000000000000..21a663849bfd
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/SNSAdaptor/index.tsx
@@ -0,0 +1,31 @@
+import type { Plugin } from '@masknet/plugin-infra'
+import { ApplicationEntry } from '@masknet/shared'
+import { base } from '../base'
+import { useState } from 'react'
+import { CrossChainBridgeDialog } from './CrossChainBridgeDialog'
+
+const sns: Plugin.SNSAdaptor.Definition = {
+ ...base,
+ init(signal, context) {},
+ ApplicationEntries: [
+ {
+ RenderEntryComponent({ disabled }) {
+ const [open, setOpen] = useState(false)
+ return (
+ <>
+ setOpen(true)}
+ />
+ setOpen(false)} />
+ >
+ )
+ },
+ defaultSortingPriority: 4,
+ },
+ ],
+}
+
+export default sns
diff --git a/packages/plugins/CrossChainBridge/src/Worker/index.ts b/packages/plugins/CrossChainBridge/src/Worker/index.ts
new file mode 100644
index 000000000000..6c436f1cb4fb
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/Worker/index.ts
@@ -0,0 +1,9 @@
+import type { Plugin } from '@masknet/plugin-infra'
+import { base } from '../base'
+
+const worker: Plugin.Worker.Definition = {
+ ...base,
+ init(signal, context) {},
+}
+
+export default worker
diff --git a/packages/plugins/CrossChainBridge/src/base.ts b/packages/plugins/CrossChainBridge/src/base.ts
new file mode 100644
index 000000000000..a593b93d23aa
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/base.ts
@@ -0,0 +1,17 @@
+import type { Plugin } from '@masknet/plugin-infra'
+import { PLUGIN_DESCRIPTION, PLUGIN_ID, PLUGIN_NAME } from './constants'
+import { languages } from './locales/languages'
+
+export const base: Plugin.Shared.Definition = {
+ ID: PLUGIN_ID,
+ name: { fallback: PLUGIN_NAME },
+ description: { fallback: PLUGIN_DESCRIPTION },
+ publisher: { name: { fallback: '' }, link: '' },
+ enableRequirement: {
+ architecture: { app: false, web: true },
+ networks: { type: 'opt-out', networks: {} },
+ target: 'stable',
+ },
+ experimentalMark: false,
+ i18n: languages,
+}
diff --git a/packages/plugins/CrossChainBridge/src/constants.tsx b/packages/plugins/CrossChainBridge/src/constants.tsx
new file mode 100644
index 000000000000..03d5249b97f2
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/constants.tsx
@@ -0,0 +1,53 @@
+import { PluginId } from '@masknet/plugin-infra'
+import {
+ CBridgeIcon,
+ ArbitrumOneBridgeIcon,
+ BobaBridgeIcon,
+ PolygonBridgeIcon,
+ RainbowBridgeIcon,
+} from './SNSAdaptor/MaskIcon'
+
+export const PLUGIN_ID = PluginId.CrossChainBridge
+export const PLUGIN_DESCRIPTION = 'A cross-chain-bridge plugin'
+export const PLUGIN_NAME = 'CrossChainBridge'
+export function getCrossChainBridge(t: Record string>) {
+ return [
+ {
+ name: 'CBridge',
+ ID: `${PLUGIN_ID}_cBridge`,
+ intro: t.cbridge_intro(),
+ icon: ,
+ isOfficial: false,
+ link: 'https://cbridge.celer.network/#/transfer',
+ },
+ {
+ name: 'Arbitrum One Bridge',
+ ID: `${PLUGIN_ID}_arbitrum_one_bridge`,
+ isOfficial: true,
+ icon: ,
+ link: 'https://bridge.arbitrum.io/',
+ },
+ {
+ name: 'BOBA Bridge',
+ ID: `${PLUGIN_ID}_boba_bridge`,
+ isOfficial: true,
+ icon: ,
+ link: 'https://gateway.boba.network/',
+ },
+ {
+ name: 'Polygon Bridge',
+ ID: `${PLUGIN_ID}_polygon_bridge`,
+ isOfficial: true,
+ icon: ,
+ link: 'https://wallet.polygon.technology/bridge/',
+ },
+ {
+ name: 'Rainbow Bridge',
+ ID: `${PLUGIN_ID}_rainbow_bridge`,
+ isOfficial: true,
+ intro: t.rainbow_bridge_intro(),
+ icon: ,
+ link: 'https://rainbowbridge.app/transfer',
+ },
+ ]
+}
diff --git a/packages/plugins/CrossChainBridge/src/env.d.ts b/packages/plugins/CrossChainBridge/src/env.d.ts
new file mode 100644
index 000000000000..868322d5ff30
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/packages/plugins/CrossChainBridge/src/index.ts b/packages/plugins/CrossChainBridge/src/index.ts
new file mode 100644
index 000000000000..ec9b1a34e7af
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/index.ts
@@ -0,0 +1,23 @@
+import { registerPlugin } from '@masknet/plugin-infra'
+import { base } from './base'
+
+export * from './constants'
+
+registerPlugin({
+ ...base,
+ SNSAdaptor: {
+ load: () => import('./SNSAdaptor'),
+ hotModuleReload: (hot) =>
+ import.meta.webpackHot && import.meta.webpackHot.accept('./SNSAdaptor', () => hot(import('./SNSAdaptor'))),
+ },
+ Dashboard: {
+ load: () => import('./Dashboard'),
+ hotModuleReload: (hot) =>
+ import.meta.webpackHot && import.meta.webpackHot.accept('./Dashboard', () => hot(import('./Dashboard'))),
+ },
+ Worker: {
+ load: () => import('./Worker'),
+ hotModuleReload: (hot) =>
+ import.meta.webpackHot && import.meta.webpackHot.accept('./Worker', () => hot(import('./Worker'))),
+ },
+})
diff --git a/packages/plugins/CrossChainBridge/src/locales/en-US.json b/packages/plugins/CrossChainBridge/src/locales/en-US.json
new file mode 100644
index 000000000000..37075139d2a9
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/locales/en-US.json
@@ -0,0 +1,6 @@
+{
+ "dialog_title": "Cross-chain",
+ "official": "Official",
+ "cbridge_intro": "Powered by Celer Network. Support $Mask!",
+ "rainbow_bridge_intro": "Bridge between or send within Ethereum, NEAR and Aurora! "
+}
diff --git a/packages/plugins/CrossChainBridge/src/locales/index.ts b/packages/plugins/CrossChainBridge/src/locales/index.ts
new file mode 100644
index 000000000000..d6ead60252e4
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/locales/index.ts
@@ -0,0 +1,6 @@
+// This file is auto generated. DO NOT EDIT
+// Run `npx gulp sync-languages` to regenerate.
+// Default fallback language in a family of languages are chosen by the alphabet order
+// To overwrite this, please overwrite packages/scripts/src/locale-kit-next/index.ts
+
+export * from './i18n_generated'
diff --git a/packages/plugins/CrossChainBridge/src/locales/ja-JP.json b/packages/plugins/CrossChainBridge/src/locales/ja-JP.json
new file mode 100644
index 000000000000..0967ef424bce
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/locales/ja-JP.json
@@ -0,0 +1 @@
+{}
diff --git a/packages/plugins/CrossChainBridge/src/locales/ko-KR.json b/packages/plugins/CrossChainBridge/src/locales/ko-KR.json
new file mode 100644
index 000000000000..0967ef424bce
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/locales/ko-KR.json
@@ -0,0 +1 @@
+{}
diff --git a/packages/plugins/CrossChainBridge/src/locales/languages.ts b/packages/plugins/CrossChainBridge/src/locales/languages.ts
new file mode 100644
index 000000000000..cff1155ab4cb
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/locales/languages.ts
@@ -0,0 +1,36 @@
+// This file is auto generated. DO NOT EDIT
+// Run `npx gulp sync-languages` to regenerate.
+// Default fallback language in a family of languages are chosen by the alphabet order
+// To overwrite this, please overwrite packages/scripts/src/locale-kit-next/index.ts
+import en_US from './en-US.json'
+import ja_JP from './ja-JP.json'
+import ko_KR from './ko-KR.json'
+import qya_AA from './qya-AA.json'
+import zh_CN from './zh-CN.json'
+import zh_TW from './zh-TW.json'
+export const languages = {
+ en: en_US,
+ ja: ja_JP,
+ ko: ko_KR,
+ qy: qya_AA,
+ 'zh-CN': zh_CN,
+ zh: zh_TW,
+}
+import { createI18NBundle } from '@masknet/shared-base'
+export const add__template__I18N = createI18NBundle('__template__', languages)
+// @ts-ignore
+if (import.meta.webpackHot) {
+ // @ts-ignore
+ import.meta.webpackHot.accept(
+ ['./en-US.json', './ja-JP.json', './ko-KR.json', './qya-AA.json', './zh-CN.json', './zh-TW.json'],
+ () =>
+ globalThis.dispatchEvent?.(
+ new CustomEvent('MASK_I18N_HMR', {
+ detail: [
+ '__template__',
+ { en: en_US, ja: ja_JP, ko: ko_KR, qy: qya_AA, 'zh-CN': zh_CN, zh: zh_TW },
+ ],
+ }),
+ ),
+ )
+}
diff --git a/packages/plugins/CrossChainBridge/src/locales/qya-AA.json b/packages/plugins/CrossChainBridge/src/locales/qya-AA.json
new file mode 100644
index 000000000000..439535f8dd96
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/locales/qya-AA.json
@@ -0,0 +1,4 @@
+{
+ "name": "crwdns13280:0crwdne13280:0",
+ "__entry__": "crwdns13282:0crwdne13282:0"
+}
diff --git a/packages/plugins/CrossChainBridge/src/locales/zh-CN.json b/packages/plugins/CrossChainBridge/src/locales/zh-CN.json
new file mode 100644
index 000000000000..0967ef424bce
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/locales/zh-CN.json
@@ -0,0 +1 @@
+{}
diff --git a/packages/plugins/CrossChainBridge/src/locales/zh-TW.json b/packages/plugins/CrossChainBridge/src/locales/zh-TW.json
new file mode 100644
index 000000000000..0967ef424bce
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/src/locales/zh-TW.json
@@ -0,0 +1 @@
+{}
diff --git a/packages/plugins/CrossChainBridge/tsconfig.json b/packages/plugins/CrossChainBridge/tsconfig.json
new file mode 100644
index 000000000000..c8c34c61f89b
--- /dev/null
+++ b/packages/plugins/CrossChainBridge/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "dist",
+ "tsBuildInfoFile": "dist/.tsbuildinfo"
+ },
+ "include": ["src", "src/**/*.json"],
+ "references": [{ "path": "../../plugin-infra" }, { "path": "../../shared" }]
+}
diff --git a/packages/plugins/CyberConnect/src/SNSAdaptor/ConnectButton.tsx b/packages/plugins/CyberConnect/src/SNSAdaptor/ConnectButton.tsx
index da4bdf16d6d2..531f6b007dad 100644
--- a/packages/plugins/CyberConnect/src/SNSAdaptor/ConnectButton.tsx
+++ b/packages/plugins/CyberConnect/src/SNSAdaptor/ConnectButton.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState, useCallback } from 'react'
import { makeStyles, MaskColorVar } from '@masknet/theme'
import { useWeb3, isSameAddress } from '@masknet/web3-shared-evm'
-import { useAccount, usePluginIDContext, NetworkPluginID } from '@masknet/plugin-infra'
+import { useAccount, useCurrentWeb3NetworkPluginID, NetworkPluginID } from '@masknet/plugin-infra'
import CyberConnect, { Env } from '@cyberlab/cyberconnect'
import { PluginCyberConnectRPC } from '../messages'
import { CircularProgress, useTheme, Typography } from '@mui/material'
@@ -96,7 +96,7 @@ export default function ConnectButton({
const [cc, setCc] = useState(null)
const [isFollowing, setIsFollowing] = useState(false)
const [isLoading, setIsLoading] = useState(false)
- const blockChainNetwork = usePluginIDContext()
+ const blockChainNetwork = useCurrentWeb3NetworkPluginID()
useAsync(async () => {
if (isSameAddress(myAddress, address)) return
const res = await PluginCyberConnectRPC.fetchFollowStatus(myAddress, address)
diff --git a/packages/plugins/FileService/src/SNSAdaptor/Preview.tsx b/packages/plugins/FileService/src/SNSAdaptor/Preview.tsx
index 808bb952cbb7..f10a66f1ed4a 100644
--- a/packages/plugins/FileService/src/SNSAdaptor/Preview.tsx
+++ b/packages/plugins/FileService/src/SNSAdaptor/Preview.tsx
@@ -7,6 +7,7 @@ import { CopyableCode } from './components/Copyable'
import type { FileInfo } from '../types'
import { resolveGatewayAPI } from '../helpers'
import urlcat from 'urlcat'
+import { openWindow } from '@masknet/shared-base-ui'
const useStyles = makeStyles()((theme) => ({
root: {
@@ -64,7 +65,7 @@ export function Preview({ info }: { info: FileInfo }) {
const onClick = (event: React.MouseEvent) => {
event.preventDefault()
event.stopPropagation()
- open(info.key ? `${link}#${info.key}` : link)
+ openWindow(info.key ? `${link}#${info.key}` : link)
}
return (
{
const linkPrefix = resolveGatewayAPI(state.provider)
const link = urlcat(linkPrefix, '/:txId', { txId: state.landingTxID })
- open(state.key ? `${link}#${state.key}` : link)
+ openWindow(state.key ? `${link}#${state.key}` : link)
}
return (
diff --git a/packages/plugins/FileService/src/SNSAdaptor/index.tsx b/packages/plugins/FileService/src/SNSAdaptor/index.tsx
index 279620daec0a..b38c6466452a 100644
--- a/packages/plugins/FileService/src/SNSAdaptor/index.tsx
+++ b/packages/plugins/FileService/src/SNSAdaptor/index.tsx
@@ -8,6 +8,8 @@ import type { FileInfo } from '../types'
import FileServiceDialog from './MainDialog'
import { Preview } from './Preview'
import { FileServiceIcon } from '@masknet/icons'
+import { CrossIsolationMessages } from '@masknet/shared-base'
+import { ApplicationEntry } from '@masknet/shared'
const definition: Plugin.SNSAdaptor.Definition = {
...base,
@@ -32,6 +34,29 @@ const definition: Plugin.SNSAdaptor.Definition = {
},
dialog: FileServiceDialog,
},
+ ApplicationEntries: [
+ {
+ RenderEntryComponent({ disabled }) {
+ return (
+
+ CrossIsolationMessages.events.requestComposition.sendToLocal({
+ reason: 'timeline',
+ open: true,
+ options: {
+ startupPlugin: base.ID,
+ },
+ })
+ }
+ />
+ )
+ },
+ defaultSortingPriority: 2,
+ },
+ ],
}
export default definition
diff --git a/packages/plugins/GoPlusSecurity/package.json b/packages/plugins/GoPlusSecurity/package.json
new file mode 100644
index 000000000000..602edf644031
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@masknet/plugin-go-plus-security",
+ "private": true,
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "dependencies": {
+ "react-feather": "^2.0.9",
+ "@masknet/icons": "workspace:*",
+ "@masknet/plugin-infra": "workspace:*",
+ "@masknet/shared": "workspace:*",
+ "@masknet/shared-base-ui": "workspace:*",
+ "@masknet/shared-base": "workspace:*",
+ "@masknet/theme": "workspace:*",
+ "@masknet/web3-providers": "workspace:*",
+ "@masknet/web3-shared-evm": "workspace:*",
+ "react-use": "^17.3.2",
+ "urlcat": "^2.0.4"
+ }
+}
diff --git a/packages/plugins/GoPlusSecurity/src/Dashboard/index.tsx b/packages/plugins/GoPlusSecurity/src/Dashboard/index.tsx
new file mode 100644
index 000000000000..c11379ee47db
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/Dashboard/index.tsx
@@ -0,0 +1,9 @@
+import type { Plugin } from '@masknet/plugin-infra'
+import { base } from '../base'
+
+const dashboard: Plugin.Dashboard.Definition = {
+ ...base,
+ init(signal) {},
+}
+
+export default dashboard
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/CheckSecurityDialog.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/CheckSecurityDialog.tsx
new file mode 100644
index 000000000000..f679c307e226
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/CheckSecurityDialog.tsx
@@ -0,0 +1,86 @@
+import { Box, DialogContent, Stack } from '@mui/material'
+import { makeStyles, MaskDialog, useStylesExtends } from '@masknet/theme'
+import { useI18N } from '../locales'
+import { SearchBox } from './components/SearchBox'
+import { useAsyncFn } from 'react-use'
+import { GoPlusLabs } from '@masknet/web3-providers'
+import { Searching } from './components/Searching'
+import { SecurityPanel } from './components/SecurityPanel'
+import { Footer } from './components/Footer'
+import { Center, TokenSecurity } from './components/Common'
+import { DefaultPlaceholder } from './components/DefaultPlaceholder'
+import { NotFound } from './components/NotFound'
+import type { ChainId } from '@masknet/web3-shared-evm'
+
+const useStyles = makeStyles()((theme) => ({
+ root: {
+ width: 600,
+ },
+ paperRoot: {
+ backgroundImage: 'none',
+ '&>h2': {
+ height: 30,
+ border: `1px solid ${theme.palette.divider}`,
+ padding: theme.spacing(1.875, 2.5, 1.875, 2.5),
+ marginBottom: 24,
+ },
+ },
+ content: {
+ width: 552,
+ height: 510,
+ maxHeight: 510,
+ paddingBottom: theme.spacing(3),
+ },
+}))
+
+export interface BuyTokenDialogProps extends withClasses {
+ open: boolean
+ onClose(): void
+}
+
+export function CheckSecurityDialog(props: BuyTokenDialogProps) {
+ const t = useI18N()
+ const classes = useStylesExtends(useStyles(), props)
+ const { open, onClose } = props
+
+ const [{ value, loading: searching, error }, onSearch] = useAsyncFn(async (chainId: ChainId, content: string) => {
+ const values = await GoPlusLabs.getTokenSecurity(chainId, [content.trim()])
+ if (!Object.keys(values ?? {}).length) throw new Error('Contract Not Found')
+ return Object.entries(values ?? {}).map((x) => ({ ...x[1], contract: x[0], chainId }))[0] as
+ | TokenSecurity
+ | undefined
+ }, [])
+
+ return (
+
+
+
+
+
+
+
+ {searching && (
+
+
+
+ )}
+ {error && !searching && }
+ {!error && !searching && value && }
+ {!error && !searching && !value && (
+
+
+
+ )}
+
+
+
+
+
+
+
+ )
+}
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/Common.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/Common.tsx
new file mode 100644
index 000000000000..c225c755e00e
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/Common.tsx
@@ -0,0 +1,53 @@
+import type { SecurityAPI } from '@masknet/web3-providers'
+import { NextIdPersonaWarningIcon, RiskIcon, SuccessIcon } from '@masknet/icons'
+import { memo, ReactNode } from 'react'
+import { Stack } from '@mui/material'
+import type { useI18N } from '../../locales'
+import type { ChainId } from '@masknet/web3-shared-evm'
+
+export type TokenSecurity = SecurityAPI.ContractSecurity &
+ SecurityAPI.TokenSecurity &
+ SecurityAPI.TradingSecurity & { contract: string; chainId: ChainId }
+
+export enum SecurityMessageLevel {
+ High = 'High',
+ Medium = 'Medium',
+ Safe = 'Safe',
+}
+
+export const Center = memo(({ children }) => (
+
+ {children}
+
+))
+
+type DefineMapping = {
+ [key in SecurityMessageLevel]: {
+ i18nKey: keyof ReturnType
+ bgColor: string
+ titleColor: string
+ icon(size?: number): ReactNode
+ }
+}
+
+export const DefineMapping: DefineMapping = {
+ [SecurityMessageLevel.High]: {
+ i18nKey: 'high_risk',
+ titleColor: '#FF5F5F',
+ bgColor: 'rgba(255, 95, 95, 0.1)',
+ icon: (size: number) => ,
+ },
+ [SecurityMessageLevel.Medium]: {
+ i18nKey: 'medium_risk',
+ titleColor: '#FFB915',
+ bgColor: 'rgba(255, 185, 21, 0.1)',
+ // TODO: Merge duplicate icon in a another PR.
+ icon: (size: number) => ,
+ },
+ [SecurityMessageLevel.Safe]: {
+ i18nKey: 'low_risk',
+ titleColor: '#60DFAB',
+ bgColor: 'rgba(119, 224, 181, 0.1)',
+ icon: (size: number) => ,
+ },
+}
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/DefaultPlaceholder.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/DefaultPlaceholder.tsx
new file mode 100644
index 000000000000..f4604ced0978
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/DefaultPlaceholder.tsx
@@ -0,0 +1,17 @@
+import { Box, Stack, Typography } from '@mui/material'
+import { SecurityIcon } from '../icons/SecurityIcon'
+import { useI18N } from '../../locales'
+
+export const DefaultPlaceholder = () => {
+ const t = useI18N()
+ return (
+
+
+
+
+
+ {t.default_placeholder()}
+
+
+ )
+}
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/Footer.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/Footer.tsx
new file mode 100644
index 000000000000..06cee1054ae7
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/Footer.tsx
@@ -0,0 +1,22 @@
+import { Box, Link, Stack, Typography } from '@mui/material'
+import { useI18N } from '../../locales'
+import { GoPlusLabLogo } from '../icons/Logo'
+import { PLUGIN_OFFICIAL_WEBSITE } from '../../constants'
+
+export const Footer = () => {
+ const t = useI18N()
+ return (
+
+
+
+
+ {t.powered_by_go_plus()}
+
+
+
+
+
+
+
+ )
+}
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/Loading.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/Loading.tsx
new file mode 100644
index 000000000000..29f8303eee8f
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/Loading.tsx
@@ -0,0 +1,22 @@
+import { makeStyles } from '@masknet/theme'
+import type { SvgIconProps } from '@mui/material'
+import { LoadingIcon } from '../icons/Loading'
+
+const useStyles = makeStyles()({
+ animated: {
+ '@keyframes loadingAnimation': {
+ '0%': {
+ transform: 'rotate(0deg)',
+ },
+ '100%': {
+ transform: 'rotate(360deg)',
+ },
+ },
+ animation: 'loadingAnimation 1s linear infinite',
+ },
+})
+
+export const LoadingAnimation = (props: SvgIconProps) => {
+ const { classes } = useStyles()
+ return
+}
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/NotFound.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/NotFound.tsx
new file mode 100644
index 000000000000..20048cad0d13
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/NotFound.tsx
@@ -0,0 +1,23 @@
+import { MaskColorVar } from '@masknet/theme'
+import { Stack, Typography } from '@mui/material'
+import { useI18N } from '../../locales'
+
+export const NotFound = () => {
+ const t = useI18N()
+ return (
+
+
+ {t.not_found_tip_title()}
+
+
+ {t.not_found_tip_network_error()}
+
+
+ {t.not_found_tip_network_chain_correct()}
+
+
+ {t.not_found_tip_network_address_not_cover()}
+
+
+ )
+}
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/RiskCard.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/RiskCard.tsx
new file mode 100644
index 000000000000..2992b7e11717
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/RiskCard.tsx
@@ -0,0 +1,66 @@
+import { memo, ReactNode } from 'react'
+import { DefineMapping, TokenSecurity } from './Common'
+import { Box, Stack, Typography } from '@mui/material'
+import type { SecurityMessage } from '../rules'
+import { useI18N } from '../../locales'
+import { makeStyles } from '@masknet/theme'
+
+const useStyles = makeStyles()((theme) => ({
+ detectionCard: {
+ backgroundColor: theme.palette.background.default,
+ borderRadius: 8,
+ },
+ icon: {
+ display: 'inline-flex',
+ lineHeight: '22px',
+ height: 22,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ header: {
+ fontSize: 14,
+ lineHeight: '22px',
+ },
+ description: {
+ fontSize: 12,
+ },
+}))
+
+interface RiskCardProps {
+ info: SecurityMessage
+ tokenSecurity: TokenSecurity
+}
+
+export const RiskCard = memo(({ info, tokenSecurity }) => {
+ const t = useI18N()
+ return (
+
+ )
+})
+
+interface RiskCardUIProps {
+ icon: ReactNode
+ title: string
+ titleColor: string
+ description?: string
+}
+
+export const RiskCardUI = memo(({ icon, title, titleColor, description }) => {
+ const { classes } = useStyles()
+ return (
+
+ {icon}
+
+
+ {title}
+
+ {description && {description} }
+
+
+ )
+})
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/SearchBox.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/SearchBox.tsx
new file mode 100644
index 000000000000..bdd180ef2958
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/SearchBox.tsx
@@ -0,0 +1,125 @@
+import { Box, Button, InputAdornment, MenuItem, Stack, Typography } from '@mui/material'
+import { useAsync } from 'react-use'
+import { memo, useMemo, useState } from 'react'
+import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
+import { makeStyles, MaskColorVar, MaskTextField, ShadowRootMenu } from '@masknet/theme'
+import { SearchIcon } from '@masknet/icons'
+import { useI18N } from '../../locales'
+import type { SecurityAPI } from '@masknet/web3-providers'
+import { GoPlusLabs } from '@masknet/web3-providers'
+import { ChainId, getChainDetailed } from '@masknet/web3-shared-evm'
+
+const useStyles = makeStyles()((theme) => ({
+ root: {
+ width: '600px',
+ },
+ content: {
+ width: '552px',
+ },
+ selectedButton: {
+ width: '100%',
+ height: '100%',
+ fontWeight: 400,
+ borderColor: theme.palette.divider,
+ },
+ searchButton: {
+ borderRadius: 8,
+ },
+ menu: {},
+ search: {
+ backgroundColor: 'transparent !important',
+ border: `solid 1px ${MaskColorVar.twitterBorderLine}`,
+ height: 48,
+ borderColor: theme.palette.divider,
+ borderRadius: 12,
+ },
+}))
+
+interface SearchBoxProps {
+ onSearch(chainId: ChainId, content: string): void
+}
+
+const DEFAULT_SEARCH_CHAIN = ChainId.Mainnet
+
+function getChainName(chain?: SecurityAPI.SupportedChain) {
+ if (!chain) return getChainDetailed(ChainId.Mainnet)?.chain
+ if (chain.id === ChainId.BSC) return getChainDetailed(ChainId.BSC)?.shortName.toUpperCase() ?? chain.name
+ return getChainDetailed(chain.id)?.chain ?? chain.name
+}
+
+export const SearchBox = memo(({ onSearch }) => {
+ const t = useI18N()
+ const { classes } = useStyles()
+ const [selectedChain, setSelectedChain] = useState()
+ const [searchContent, setSearchSearchContent] = useState()
+ const [anchorEl, setAnchorEl] = useState(null)
+
+ const onClose = () => setAnchorEl(null)
+ const onOpen = (event: React.MouseEvent) => setAnchorEl(event.currentTarget)
+
+ const { value: supportedChains = [] } = useAsync(GoPlusLabs.getSupportedChain, [])
+
+ const menuElements = useMemo(() => {
+ if (!supportedChains.length) return
+ setSelectedChain(supportedChains[0])
+ return (
+ supportedChains.map((chain) => {
+ return (
+ {
+ setSelectedChain(chain)
+ onClose()
+ }}>
+ {getChainName(chain)}
+
+ )
+ }) ?? []
+ )
+ }, [supportedChains.length])
+
+ return (
+
+
+
+
+ {getChainName(selectedChain)}
+
+
+
+
+
+
+ {
+ if (e.key !== 'Enter') return
+ onSearch(selectedChain?.id ?? DEFAULT_SEARCH_CHAIN, searchContent ?? '')
+ }}
+ onChange={(e) => setSearchSearchContent(e.target.value)}
+ InputProps={{
+ classes: { root: classes.search },
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+ onSearch(selectedChain?.id ?? DEFAULT_SEARCH_CHAIN, searchContent ?? '')}
+ variant="contained">
+ {t.search()}
+
+
+
+ {menuElements}
+
+
+ )
+})
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/Searching.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/Searching.tsx
new file mode 100644
index 000000000000..20a7b1521f0d
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/Searching.tsx
@@ -0,0 +1,10 @@
+import { Stack } from '@mui/material'
+import { LoadingAnimation } from './Loading'
+
+export const Searching = () => {
+ return (
+
+
+
+ )
+}
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/SecurityPanel.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/SecurityPanel.tsx
new file mode 100644
index 000000000000..a5ee5c35b44a
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/SecurityPanel.tsx
@@ -0,0 +1,151 @@
+import { Collapse, Link, Stack, Typography } from '@mui/material'
+import { useI18N } from '../../locales'
+import { ExternalLink } from 'react-feather'
+import { makeStyles } from '@masknet/theme'
+import { memo, useMemo, useState } from 'react'
+import { DefineMapping, SecurityMessageLevel, TokenSecurity } from './Common'
+import { SecurityMessages } from '../rules'
+import { RiskCard, RiskCardUI } from './RiskCard'
+import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
+import { useTheme } from '@mui/system'
+import { resolveGoLabLink } from '../../utils/helper'
+import { TokenPanel } from './TokenPanel'
+
+interface TokenCardProps {
+ tokenSecurity: TokenSecurity
+}
+
+const useStyles = makeStyles()((theme) => ({
+ header: {
+ fontWeight: 500,
+ fontSize: 18,
+ },
+ root: {
+ width: '600px',
+ },
+ detectionCard: {
+ backgroundColor: theme.palette.background.default,
+ },
+ detectionCollection: {
+ overflowY: 'auto',
+ '&::-webkit-scrollbar': {
+ display: 'none',
+ },
+ },
+}))
+
+const LIST_HEIGHT = {
+ min: 154,
+ max: 308,
+}
+
+export const SecurityPanel = memo(({ tokenSecurity }) => {
+ const { classes } = useStyles()
+ const t = useI18N()
+ const theme = useTheme()
+
+ const [isCollapse, setCollapse] = useState(false)
+
+ const makeMessageList =
+ tokenSecurity.is_whitelisted === '1'
+ ? []
+ : SecurityMessages.filter(
+ (x) =>
+ x.condition(tokenSecurity) &&
+ x.level !== SecurityMessageLevel.Safe &&
+ !x.shouldHide(tokenSecurity),
+ ).sort((a) => (a.level === SecurityMessageLevel.High ? -1 : 1))
+
+ const riskyFactors = makeMessageList.filter((x) => x.level === SecurityMessageLevel.High).length
+ const attentionFactors = makeMessageList.filter((x) => x.level === SecurityMessageLevel.Medium).length
+
+ const securityMessageLevel = useMemo(() => {
+ if (riskyFactors) return SecurityMessageLevel.High
+ if (attentionFactors) return SecurityMessageLevel.Medium
+ return SecurityMessageLevel.Safe
+ }, [riskyFactors, attentionFactors])
+
+ return (
+
+
+
+
+
+ {t.token_info()}
+
+ setCollapse(!isCollapse)}
+ sx={{ fontSize: 15, cursor: 'pointer' }}
+ />
+
+
+
+ {t.more_details()}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t.security_detection()}
+
+
+ {riskyFactors !== 0 && (
+
+ {DefineMapping[SecurityMessageLevel.High].icon(14)}
+
+ {riskyFactors > 1
+ ? t.risky_factors({ quantity: riskyFactors.toString() })
+ : t.risky_factor({ quantity: riskyFactors.toString() })}
+
+
+ )}
+ {attentionFactors !== 0 && (
+
+ {DefineMapping[SecurityMessageLevel.Medium].icon(14)}
+
+ {attentionFactors > 1
+ ? t.attention_factors({ quantity: attentionFactors.toString() })
+ : t.attention_factor({ quantity: attentionFactors.toString() })}
+
+
+ )}
+
+
+
+
+ {makeMessageList.map((x, i) => (
+
+ ))}
+ {(!makeMessageList.length || securityMessageLevel === SecurityMessageLevel.Safe) && (
+
+ )}
+
+
+
+
+ )
+})
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/TokenPanel.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/TokenPanel.tsx
new file mode 100644
index 000000000000..3844db22dddd
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/TokenPanel.tsx
@@ -0,0 +1,166 @@
+import { Link, Stack, Tooltip, Typography } from '@mui/material'
+import { DefineMapping, SecurityMessageLevel, TokenSecurity } from './Common'
+import { useI18N } from '../../locales'
+import React from 'react'
+import { useTheme } from '@mui/system'
+import { ExternalLink } from 'react-feather'
+import { makeStyles, usePortalShadowRoot } from '@masknet/theme'
+import {
+ ERC20Token,
+ formatCurrency,
+ formatEthereumAddress,
+ resolveAddressLinkOnExplorer,
+ resolveTokenLinkOnExplorer,
+} from '@masknet/web3-shared-evm'
+
+const useStyles = makeStyles()((theme) => ({
+ card: {
+ borderColor: theme.palette.divider,
+ borderStyle: 'solid',
+ borderWidth: 1,
+ padding: theme.spacing(1.5),
+ borderRadius: 9,
+ },
+ subtitle: {
+ color: theme.palette.text.secondary,
+ fontWeight: 400,
+ fontSize: 14,
+ },
+ cardValue: {
+ color: theme.palette.text.primary,
+ fontSize: 14,
+ },
+ tooltip: {
+ color: theme.palette.text.buttonText,
+ fontSize: 12,
+ },
+}))
+
+const DEFAULT_PLACEHOLDER = '--'
+
+function formatTotalSupply(total?: number) {
+ if (!total) return DEFAULT_PLACEHOLDER
+ return formatCurrency(total)
+}
+
+interface TokenPanelProps {
+ tokenSecurity: TokenSecurity
+ securityMessageLevel: SecurityMessageLevel
+}
+
+export const TokenPanel = React.forwardRef(({ tokenSecurity, securityMessageLevel }: TokenPanelProps, ref) => {
+ const t = useI18N()
+ const { classes } = useStyles()
+ const theme = useTheme()
+
+ const totalSupply = usePortalShadowRoot((container) => {
+ return (
+ theme.palette.text.buttonText} className={classes.tooltip}>
+ {tokenSecurity.total_supply}
+
+ }>
+ {formatTotalSupply(tokenSecurity.total_supply)}
+
+ )
+ })
+
+ return (
+
+
+
+ {DefineMapping[securityMessageLevel].icon(33)}
+
+ {t[DefineMapping[securityMessageLevel].i18nKey]({ quantity: '', rate: '' })}
+
+
+
+
+ {t.token_info_token_name()}
+
+ {tokenSecurity.token_symbol}({tokenSecurity.token_name})
+
+
+
+ {t.token_info_token_contract_address()}
+
+
+ {tokenSecurity.contract
+ ? formatEthereumAddress(tokenSecurity.contract, 4)
+ : DEFAULT_PLACEHOLDER}
+
+
+
+
+
+
+
+ {t.token_info_contract_creator()}
+
+
+ {tokenSecurity.creator_address
+ ? formatEthereumAddress(tokenSecurity.creator_address ?? '', 4)
+ : DEFAULT_PLACEHOLDER}
+
+ {tokenSecurity.creator_address && (
+
+
+
+ )}
+
+
+
+ {t.token_info_contract_owner()}
+
+
+ {tokenSecurity.owner_address
+ ? formatEthereumAddress(tokenSecurity.owner_address ?? '', 4)
+ : DEFAULT_PLACEHOLDER}
+
+ {tokenSecurity.owner_address && (
+
+
+
+ )}
+
+
+
+ {t.token_info_total_supply()}
+ {totalSupply}
+
+
+
+
+ )
+})
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/icons/Loading.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/icons/Loading.tsx
new file mode 100644
index 000000000000..f83c1449844d
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/icons/Loading.tsx
@@ -0,0 +1,21 @@
+import { createIcon } from '@masknet/icons'
+
+export const LoadingIcon = createIcon(
+ 'LoadingIcon',
+ <>
+
+
+ >,
+ '0 0 20 20',
+)
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/icons/Logo.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/icons/Logo.tsx
new file mode 100644
index 000000000000..a7737f0e35bc
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/icons/Logo.tsx
@@ -0,0 +1,30 @@
+import { createIcon } from '@masknet/icons'
+
+export const GoPlusLabLogo = createIcon(
+ 'GoPlusLabLogo',
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >,
+ '0 0 29 24',
+ [29, 24],
+)
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/icons/SecurityIcon.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/icons/SecurityIcon.tsx
new file mode 100644
index 000000000000..911793ca0e92
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/icons/SecurityIcon.tsx
@@ -0,0 +1,53 @@
+import { createIcon } from '@masknet/icons'
+
+export const SecurityIcon = createIcon(
+ 'SecurityIcon',
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
+ '0 0 48 48',
+)
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/index.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/index.tsx
new file mode 100644
index 000000000000..1273bd57d506
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/index.tsx
@@ -0,0 +1,30 @@
+import type { Plugin } from '@masknet/plugin-infra'
+import { base } from '../base'
+import { CheckSecurityDialog } from './CheckSecurityDialog'
+import { useState } from 'react'
+import { ApplicationEntry } from '@masknet/shared'
+
+const sns: Plugin.SNSAdaptor.Definition = {
+ ...base,
+ init(signal) {},
+ ApplicationEntries: [
+ {
+ RenderEntryComponent() {
+ const [open, setOpen] = useState(false)
+ return (
+ <>
+ setOpen(true)}
+ />
+ {open && setOpen(false)} />}
+ >
+ )
+ },
+ defaultSortingPriority: 12,
+ },
+ ],
+}
+
+export default sns
diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/rules.ts b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/rules.ts
new file mode 100644
index 000000000000..8c31a6f1e438
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/rules.ts
@@ -0,0 +1,324 @@
+import { SecurityMessageLevel, TokenSecurity } from './components/Common'
+import type { useI18N } from '../locales'
+import parseInt from 'lodash-es/parseInt'
+
+export type I18nOptions = 'rate' | 'quantity'
+
+enum SecurityType {
+ Contract = 'contract-security',
+ Transaction = 'transaction-security',
+ Info = 'info-security',
+}
+
+export interface SecurityMessage {
+ type: SecurityType
+ level: SecurityMessageLevel
+ condition(info: TokenSecurity): boolean
+ titleKey: keyof ReturnType
+ messageKey: keyof ReturnType
+ i18nParams?: (info: TokenSecurity) => { [key in I18nOptions]: string }
+ shouldHide(info: TokenSecurity): boolean
+}
+
+const percentageToNumber = (value?: string) => parseInt((value ?? '').replace('%', '')) * 100
+const isUnset = (name: keyof TokenSecurity) => (info: TokenSecurity) => info[name] === undefined
+
+export const SecurityMessages: SecurityMessage[] = [
+ // open source
+ {
+ type: SecurityType.Contract,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.is_open_source === '1',
+ titleKey: 'risk_contract_source_code_verified_title',
+ messageKey: 'risk_contract_source_code_verified_body',
+ shouldHide: isUnset('is_open_source'),
+ },
+ {
+ type: SecurityType.Contract,
+ level: SecurityMessageLevel.High,
+ condition: (info: TokenSecurity) => info.is_open_source === '0',
+ titleKey: 'risk_contract_source_code_not_verified_title',
+ messageKey: 'risk_contract_source_code_not_verified_body',
+ shouldHide: isUnset('is_open_source'),
+ },
+ // proxy
+ {
+ type: SecurityType.Contract,
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.is_proxy === '1',
+ titleKey: 'risk_proxy_contract_title',
+ messageKey: 'risk_proxy_contract_body',
+ shouldHide: isUnset('is_proxy'),
+ },
+ {
+ type: SecurityType.Contract,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.is_proxy === '0',
+ titleKey: 'risk_no_proxy_title',
+ messageKey: 'risk_no_proxy_body',
+ shouldHide: isUnset('is_proxy'),
+ },
+ // mint
+ {
+ type: SecurityType.Contract,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.is_mintable === '0',
+ titleKey: 'risk_no_mint_function_title',
+ messageKey: 'risk_no_mint_function_body',
+ shouldHide: isUnset('is_mintable'),
+ },
+ {
+ type: SecurityType.Contract,
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.is_mintable === '1',
+ titleKey: 'risk_mint_function_title',
+ messageKey: 'risk_mint_function_body',
+ shouldHide: isUnset('is_mintable'),
+ },
+ // owner change balance
+ {
+ type: SecurityType.Contract,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.owner_change_balance === '0',
+ titleKey: 'risk_owner_can_not_change_balance_title',
+ messageKey: 'risk_owner_can_not_change_balance_body',
+ shouldHide: isUnset('owner_change_balance'),
+ },
+ {
+ type: SecurityType.Contract,
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.owner_change_balance === '1',
+ titleKey: 'risk_owner_change_balance_title',
+ messageKey: 'risk_owner_change_balance_body',
+ shouldHide: isUnset('owner_change_balance'),
+ },
+ // can take back ownership
+ {
+ type: SecurityType.Contract,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.can_take_back_ownership === '0',
+ titleKey: 'risk_no_can_take_back_ownership_title',
+ messageKey: 'risk_no_can_take_back_ownership_body',
+ shouldHide: isUnset('can_take_back_ownership'),
+ },
+ {
+ type: SecurityType.Contract,
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.can_take_back_ownership === '1',
+ titleKey: 'risk_can_take_back_ownership_title',
+ messageKey: 'risk_can_take_back_ownership_body',
+ shouldHide: isUnset('can_take_back_ownership'),
+ },
+ // buy tax
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => percentageToNumber(info.buy_tax) < 10,
+ titleKey: 'risk_buy_tax_title',
+ messageKey: 'risk_buy_tax_body',
+ i18nParams: (info: TokenSecurity) => ({
+ rate: `${percentageToNumber(info.buy_tax)}%`,
+ quantity: '',
+ }),
+ shouldHide: isUnset('buy_tax'),
+ },
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) =>
+ percentageToNumber(info.buy_tax) >= 10 && percentageToNumber(info.buy_tax) < 50,
+ titleKey: 'risk_buy_tax_title',
+ messageKey: 'risk_buy_tax_body',
+ i18nParams: (info: TokenSecurity) => ({
+ rate: `${percentageToNumber(info.buy_tax)}%`,
+ quantity: '',
+ }),
+ shouldHide: isUnset('buy_tax'),
+ },
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.High,
+ condition: (info: TokenSecurity) => percentageToNumber(info.buy_tax) >= 50,
+ titleKey: 'risk_buy_tax_title',
+ messageKey: 'risk_buy_tax_body',
+ i18nParams: (info: TokenSecurity) => ({
+ rate: `${percentageToNumber(info.buy_tax)}%`,
+ quantity: '',
+ }),
+ shouldHide: isUnset('buy_tax'),
+ },
+ // sell tax
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => percentageToNumber(info.sell_tax) < 10,
+ titleKey: 'risk_sell_tax_title',
+ messageKey: 'risk_sell_tax_body',
+ i18nParams: (info: TokenSecurity) => ({
+ rate: `${percentageToNumber(info.sell_tax)}%`,
+ quantity: '',
+ }),
+ shouldHide: isUnset('sell_tax'),
+ },
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) =>
+ percentageToNumber(info.sell_tax) >= 10 && percentageToNumber(info.sell_tax) < 50,
+ titleKey: 'risk_sell_tax_title',
+ messageKey: 'risk_sell_tax_body',
+ i18nParams: (info: TokenSecurity) => ({
+ rate: `${percentageToNumber(info.sell_tax)}%`,
+ quantity: '',
+ }),
+ shouldHide: isUnset('sell_tax'),
+ },
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.High,
+ condition: (info: TokenSecurity) => percentageToNumber(info.sell_tax) >= 50,
+ titleKey: 'risk_sell_tax_title',
+ messageKey: 'risk_sell_tax_body',
+ i18nParams: (info: TokenSecurity) => ({
+ rate: `${percentageToNumber(info.sell_tax)}%`,
+ quantity: '',
+ }),
+ shouldHide: isUnset('sell_tax'),
+ },
+ // honeypot
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.is_honeypot === '0',
+ titleKey: 'risk_is_not_honeypot_title',
+ messageKey: 'risk_is_not_honeypot_body',
+ shouldHide: isUnset('is_honeypot'),
+ },
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.High,
+ condition: (info: TokenSecurity) => info.is_honeypot === '1',
+ titleKey: 'risk_is_honeypot_title',
+ messageKey: 'risk_is_honeypot_body',
+ shouldHide: isUnset('is_honeypot'),
+ },
+ // transfer_pausable
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.transfer_pausable === '0',
+ titleKey: 'risk_no_code_transfer_pausable_title',
+ messageKey: 'risk_no_code_transfer_pausable_title',
+ shouldHide: isUnset('transfer_pausable'),
+ },
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.transfer_pausable === '1',
+ titleKey: 'risk_transfer_pausable_title',
+ messageKey: 'risk_transfer_pausable_body',
+ shouldHide: isUnset('transfer_pausable'),
+ },
+ // anti whale
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.is_anti_whale === '0',
+ titleKey: 'risk_is_no_anti_whale_title',
+ messageKey: 'risk_is_no_anti_whale_body',
+ shouldHide: isUnset('is_anti_whale'),
+ },
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.is_anti_whale === '1',
+ titleKey: 'risk_is_anti_whale_title',
+ messageKey: 'risk_is_anti_whale_body',
+ shouldHide: isUnset('is_anti_whale'),
+ },
+ // slippage modifiable
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.slippage_modifiable === '0',
+ titleKey: 'risk_not_slippage_modifiable_title',
+ messageKey: 'risk_not_slippage_modifiable_body',
+ shouldHide: isUnset('slippage_modifiable'),
+ },
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.slippage_modifiable === '1',
+ titleKey: 'risk_slippage_modifiable_title',
+ messageKey: 'risk_slippage_modifiable_body',
+ shouldHide: isUnset('slippage_modifiable'),
+ },
+ // black list
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.is_blacklisted === '0',
+ titleKey: 'risk_not_is_blacklisted_title',
+ messageKey: 'risk_not_is_blacklisted_body',
+ shouldHide: isUnset('is_blacklisted'),
+ },
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.is_blacklisted === '1',
+ titleKey: 'risk_is_blacklisted_title',
+ messageKey: 'risk_is_blacklisted_body',
+ shouldHide: isUnset('is_blacklisted'),
+ },
+ // white list
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.is_whitelisted === '0',
+ titleKey: 'risk_not_is_whitelisted_title',
+ messageKey: 'risk_not_is_whitelisted_body',
+ shouldHide: isUnset('is_whitelisted'),
+ },
+ {
+ type: SecurityType.Transaction,
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.is_whitelisted === '1',
+ titleKey: 'risk_is_whitelisted_title',
+ messageKey: 'risk_is_whitelisted_body',
+ shouldHide: isUnset('is_whitelisted'),
+ },
+ // true token
+ {
+ type: SecurityType.Info,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.is_true_token === '1',
+ titleKey: 'risk_is_true_token_title',
+ messageKey: 'risk_is_true_token_body',
+ shouldHide: isUnset('is_true_token'),
+ },
+ {
+ type: SecurityType.Info,
+ level: SecurityMessageLevel.High,
+ condition: (info: TokenSecurity) => info.is_true_token === '0',
+ titleKey: 'risk_not_is_true_token_title',
+ messageKey: 'risk_not_is_true_token_body',
+ shouldHide: isUnset('is_true_token'),
+ },
+ // Airdrop scam
+ {
+ type: SecurityType.Info,
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.is_airdrop_scam === '0',
+ titleKey: 'risk_is_airdrop_scam_title',
+ messageKey: 'risk_is_airdrop_scam_body',
+ shouldHide: isUnset('is_airdrop_scam'),
+ },
+ {
+ type: SecurityType.Info,
+ level: SecurityMessageLevel.High,
+ condition: (info: TokenSecurity) => info.is_airdrop_scam === '1',
+ titleKey: 'risk_not_is_airdrop_scam_title',
+ messageKey: 'risk_not_is_airdrop_scam_body',
+ shouldHide: isUnset('is_airdrop_scam'),
+ },
+]
diff --git a/packages/plugins/GoPlusSecurity/src/Worker/index.ts b/packages/plugins/GoPlusSecurity/src/Worker/index.ts
new file mode 100644
index 000000000000..6c436f1cb4fb
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/Worker/index.ts
@@ -0,0 +1,9 @@
+import type { Plugin } from '@masknet/plugin-infra'
+import { base } from '../base'
+
+const worker: Plugin.Worker.Definition = {
+ ...base,
+ init(signal, context) {},
+}
+
+export default worker
diff --git a/packages/plugins/GoPlusSecurity/src/assets/security-icon.png b/packages/plugins/GoPlusSecurity/src/assets/security-icon.png
new file mode 100644
index 000000000000..2a0d93498a54
Binary files /dev/null and b/packages/plugins/GoPlusSecurity/src/assets/security-icon.png differ
diff --git a/packages/plugins/GoPlusSecurity/src/base.ts b/packages/plugins/GoPlusSecurity/src/base.ts
new file mode 100644
index 000000000000..c19fe3023fe1
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/base.ts
@@ -0,0 +1,17 @@
+import type { Plugin } from '@masknet/plugin-infra'
+import { PLUGIN_DESCRIPTION, PLUGIN_ID, PLUGIN_NAME } from './constants'
+import { languages } from './locales/languages'
+
+export const base: Plugin.Shared.Definition = {
+ ID: PLUGIN_ID,
+ icon: '',
+ name: { fallback: PLUGIN_NAME },
+ description: { fallback: PLUGIN_DESCRIPTION },
+ publisher: { name: { fallback: 'Mask Network' }, link: 'https://mask.io/' },
+ enableRequirement: {
+ architecture: { app: false, web: true },
+ networks: { type: 'opt-out', networks: {} },
+ target: 'stable',
+ },
+ i18n: languages,
+}
diff --git a/packages/plugins/GoPlusSecurity/src/constants.ts b/packages/plugins/GoPlusSecurity/src/constants.ts
new file mode 100644
index 000000000000..313d7a66f428
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/constants.ts
@@ -0,0 +1,7 @@
+import { PluginId } from '@masknet/plugin-infra'
+
+export const PLUGIN_ID = PluginId.GoPlusSecurity
+export const PLUGIN_META_KEY = `${PluginId.GoPlusSecurity}:1`
+export const PLUGIN_DESCRIPTION = 'Go+ Security Engine'
+export const PLUGIN_NAME = 'GoPlusSecurity'
+export const PLUGIN_OFFICIAL_WEBSITE = 'https://gopluslabs.io'
diff --git a/packages/plugins/GoPlusSecurity/src/env.d.ts b/packages/plugins/GoPlusSecurity/src/env.d.ts
new file mode 100644
index 000000000000..868322d5ff30
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/packages/plugins/GoPlusSecurity/src/index.ts b/packages/plugins/GoPlusSecurity/src/index.ts
new file mode 100644
index 000000000000..ec9b1a34e7af
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/index.ts
@@ -0,0 +1,23 @@
+import { registerPlugin } from '@masknet/plugin-infra'
+import { base } from './base'
+
+export * from './constants'
+
+registerPlugin({
+ ...base,
+ SNSAdaptor: {
+ load: () => import('./SNSAdaptor'),
+ hotModuleReload: (hot) =>
+ import.meta.webpackHot && import.meta.webpackHot.accept('./SNSAdaptor', () => hot(import('./SNSAdaptor'))),
+ },
+ Dashboard: {
+ load: () => import('./Dashboard'),
+ hotModuleReload: (hot) =>
+ import.meta.webpackHot && import.meta.webpackHot.accept('./Dashboard', () => hot(import('./Dashboard'))),
+ },
+ Worker: {
+ load: () => import('./Worker'),
+ hotModuleReload: (hot) =>
+ import.meta.webpackHot && import.meta.webpackHot.accept('./Worker', () => hot(import('./Worker'))),
+ },
+})
diff --git a/packages/plugins/GoPlusSecurity/src/locales/en-US.json b/packages/plugins/GoPlusSecurity/src/locales/en-US.json
new file mode 100644
index 000000000000..d8ad93ff5a73
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/locales/en-US.json
@@ -0,0 +1,87 @@
+{
+ "dialog_title": "Check Security",
+ "powered_by_go_plus": "Powered by Go+",
+ "high_risk": "High Risk",
+ "low_risk": "Low Risk",
+ "medium_risk": "Medium Risk",
+ "search": "Search",
+ "search_input_placeholder": "Please enter token contract address.",
+ "token_info": "Token info",
+ "more_details": "More Details",
+ "security_detection": "Security Detection",
+ "risky_factors": "{{quantity}} Risky factors",
+ "attention_factors": "{{quantity}} Attention factors",
+ "risky_factor": "{{quantity}} Risky factor",
+ "attention_factor": "{{quantity}} Attention factor",
+ "security_info_code_not_verify_title": "Contract source code not verified",
+ "security_info_code_not_verify_message": "This token contract has not been verified. We cannot check the contract code for details. Unsourced token contracts are likely to have malicious functions to defraud users of their assets",
+ "security_info_functions_that_can_suspend_trading_title": "Functions that can suspend trading",
+ "security_info_functions_that_can_suspend_trading_message": "If a suspendable code is included, the token maybe neither be bought nor sold (honeypot risk). ",
+ "token_info_token_name": "Token Name",
+ "token_info_token_contract_address": "Token Contract Address",
+ "token_info_contract_creator": "Contract Creator",
+ "token_info_contract_owner": "Contract Owner",
+ "token_info_total_supply": "Total Supply",
+ "default_placeholder": "Note: Scams detections might not be 100% guaranteed.",
+ "not_found_tip_title": "Results not found now. it might be chain network error, on-chain data abnormal or the token address is not covered now. please check as followings:",
+ "not_found_tip_network_error": "1. Make sure network is working;",
+ "not_found_tip_network_chain_correct": "2. Mask sure the chain network or token address is correct;",
+ "not_found_tip_network_address_not_cover": "3. Token address is not covered now, it might take more than 60s to get contract information again. Please try it later.",
+ "risk_safe_description": "This token has no risky or attention factors.",
+ "risk_contract_source_code_verified_title": "Contract source code verified",
+ "risk_contract_source_code_verified_body": "This token contract is open source. You can check the contract code for details. Unsourced token contracts are likely to have malicious functions to defraud their users of their assets.",
+ "risk_contract_source_code_not_verified_title": "Contract source code not verified",
+ "risk_contract_source_code_not_verified_body": "This token contract has not been verified. We cannot check the contract code for details. Unsourced token contracts are likely to have malicious functions to defraud users of their assets.",
+ "risk_proxy_contract_title": "Proxy contract",
+ "risk_proxy_contract_body": "This contract is an Admin Upgradeability Proxy. The proxy contract means the contract owner can modifiy the function of the token and could possibly effect the price.There is possibly a way for the team to Rug or Scam. Please confirm the details with the project team before buying.",
+ "risk_no_proxy_title": "No proxy",
+ "risk_no_proxy_body": "There is no proxy in the contract. The proxy contract means contract owner can modifiy the function of the token and possibly effect the price.",
+ "risk_mint_function_title": "Mint function",
+ "risk_mint_function_body": "The contract may contain additional issuance functions, which could maybe generate a large number of tokens, resulting in significant fluctuations in token prices. It is recommended to confirm with the project team whether it complies with the token issuance instructions.",
+ "risk_no_mint_function_title": "No mint function",
+ "risk_no_mint_function_body": "Mint function is transparent or non-existent. Hidden mint functions may increase the amount of tokens in circulation and effect the price of the token.",
+ "risk_can_take_back_ownership_title": "Functions with retrievable ownership",
+ "risk_can_take_back_ownership_body": "If this function exists, it is possible for the project owner to regain ownership even after relinquishing it",
+ "risk_no_can_take_back_ownership_title": "No function found that retrieves ownership",
+ "risk_no_can_take_back_ownership_body": "If this function exists, it is possible for the project owner to regain ownership even after relinquishing it",
+ "risk_owner_change_balance_title": "Owner can change balance",
+ "risk_owner_change_balance_body": "The contract owner has the authority to modify the balance of tokens at other addresses, which may result in a loss of assets.",
+ "risk_owner_can_not_change_balance_title": "Owner can't change balance",
+ "risk_owner_can_not_change_balance_body": "The contract owner is not found to have the authority to modify the balance of tokens at other addresses.",
+ "risk_buy_tax_title": "Buy Tax: {{rate}}",
+ "risk_buy_tax_body": "Above 10% may be considered a high tax rate. More than 50% tax rate means may not be tradable.",
+ "risk_sell_tax_title": "Sell Tax: {{rate}}",
+ "risk_sell_tax_body": "Above 10% may be considered a high tax rate. More than 50% tax rate means may not be tradable.",
+ "risk_is_honeypot_title": "May the token is a honeypot.",
+ "risk_is_honeypot_body": "This token contract has a code that states that it cannot be sold. Maybe this is a honeypot.",
+ "risk_is_not_honeypot_title": "This does not appear to be a honeypot.",
+ "risk_is_not_honeypot_body": "We are not aware of any code that prevents the sale of tokens.",
+ "risk_transfer_pausable_title": "Functions that can suspend trading",
+ "risk_transfer_pausable_body": "If a suspendable code is included, the token maybe neither be bought nor sold (honeypot risk).",
+ "risk_no_code_transfer_pausable_title": "No codes found to suspend trading.",
+ "risk_no_code_transfer_pausable_body": "If a suspendable code is included, the token maybe neither be bought nor sold (honeypot risk).",
+ "risk_is_anti_whale_title": " Anti_whale(Limited number of transactions)",
+ "risk_is_anti_whale_body": "The number of token transactions is limited. The number of scam token transactions may be limited (honeypot risk).",
+ "risk_is_no_anti_whale_title": "No anti_whale(Unlimited number of transactions)",
+ "risk_is_no_anti_whale_body": "There is no limit to the number of token transactions. The number of scam token transactions may be limited (honeypot risk).",
+ "risk_slippage_modifiable_title": "Tax can be modified",
+ "risk_slippage_modifiable_body": " The contract owner may contain the authority to modify the transaction tax. If the transaction tax is increased to more than 49%, the tokens will not be able to be traded (honeypot risk).",
+ "risk_not_slippage_modifiable_title": "Tax cannot be modified",
+ "risk_not_slippage_modifiable_body": "The contract owner may not contain the authority to modify the transaction tax. If the transaction tax is increased to more than 49%, the tokens will not be able to be traded (honeypot risk).",
+ "risk_is_blacklisted_title": "Blacklist function",
+ "risk_is_blacklisted_body": "The blacklist function is included. Some addresses may not be able to trade normally (honeypot risk).",
+ "risk_not_is_blacklisted_title": "No blacklist",
+ "risk_not_is_blacklisted_body": "The blacklist function is not included. If there is a blacklist, some addresses may not be able to trade normally (honeypot risk).",
+ "risk_is_whitelisted_title": "Whitelist function",
+ "risk_is_whitelisted_body": "The whitelist function is included. Some addresses may not be able to trade normally (honeypot risk).",
+ "risk_not_is_whitelisted_title": "No whitelist",
+ "risk_not_is_whitelisted_body": "The whitelist function is not included. If there is a whitelist, some addresses may not be able to trade normally (honeypot risk).",
+ "risk_is_true_token_title": "True Token",
+ "risk_is_true_token_body": "This token is issued by its declared team. Some scams will create a well-known token with the same name to defraud their users of their assets.",
+ "risk_not_is_true_token_title": "Fake Token",
+ "risk_not_is_true_token_body": "This token is not issued by its declared team. Some scams will create a well-known token with the same name to defraud their users of their assets.",
+ "risk_is_airdrop_scam_title": "Airdrop Scam",
+ "risk_is_airdrop_scam_body": "You may lose your assets if giving approval to the website of this token.",
+ "risk_not_is_airdrop_scam_title": "No Airdrop Scam",
+ "risk_not_is_airdrop_scam_body": "This is not an airdrop scam. Many scams attract users through airdrops."
+}
diff --git a/packages/plugins/GoPlusSecurity/src/locales/index.ts b/packages/plugins/GoPlusSecurity/src/locales/index.ts
new file mode 100644
index 000000000000..d6ead60252e4
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/locales/index.ts
@@ -0,0 +1,6 @@
+// This file is auto generated. DO NOT EDIT
+// Run `npx gulp sync-languages` to regenerate.
+// Default fallback language in a family of languages are chosen by the alphabet order
+// To overwrite this, please overwrite packages/scripts/src/locale-kit-next/index.ts
+
+export * from './i18n_generated'
diff --git a/packages/plugins/GoPlusSecurity/src/locales/ja-JP.json b/packages/plugins/GoPlusSecurity/src/locales/ja-JP.json
new file mode 100644
index 000000000000..0967ef424bce
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/locales/ja-JP.json
@@ -0,0 +1 @@
+{}
diff --git a/packages/plugins/GoPlusSecurity/src/locales/ko-KR.json b/packages/plugins/GoPlusSecurity/src/locales/ko-KR.json
new file mode 100644
index 000000000000..0967ef424bce
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/locales/ko-KR.json
@@ -0,0 +1 @@
+{}
diff --git a/packages/plugins/GoPlusSecurity/src/locales/languages.ts b/packages/plugins/GoPlusSecurity/src/locales/languages.ts
new file mode 100644
index 000000000000..8d0cd55c479a
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/locales/languages.ts
@@ -0,0 +1,34 @@
+// This file is auto generated. DO NOT EDIT
+// Run `npx gulp sync-languages` to regenerate.
+// Default fallback language in a family of languages are chosen by the alphabet order
+// To overwrite this, please overwrite packages/scripts/src/locale-kit-next/index.ts
+import en_US from './en-US.json'
+import ja_JP from './ja-JP.json'
+import ko_KR from './ko-KR.json'
+import qya_AA from './qya-AA.json'
+import zh_CN from './zh-CN.json'
+import zh_TW from './zh-TW.json'
+export const languages = {
+ en: en_US,
+ ja: ja_JP,
+ ko: ko_KR,
+ qy: qya_AA,
+ 'zh-CN': zh_CN,
+ zh: zh_TW,
+}
+// @ts-ignore
+if (import.meta.webpackHot) {
+ // @ts-ignore
+ import.meta.webpackHot.accept(
+ ['./en-US.json', './ja-JP.json', './ko-KR.json', './qya-AA.json', './zh-CN.json', './zh-TW.json'],
+ () =>
+ globalThis.dispatchEvent?.(
+ new CustomEvent('MASK_I18N_HMR', {
+ detail: [
+ 'io.mask.debugger',
+ { en: en_US, ja: ja_JP, ko: ko_KR, qy: qya_AA, 'zh-CN': zh_CN, zh: zh_TW },
+ ],
+ }),
+ ),
+ )
+}
diff --git a/packages/plugins/GoPlusSecurity/src/locales/qya-AA.json b/packages/plugins/GoPlusSecurity/src/locales/qya-AA.json
new file mode 100644
index 000000000000..0967ef424bce
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/locales/qya-AA.json
@@ -0,0 +1 @@
+{}
diff --git a/packages/plugins/GoPlusSecurity/src/locales/zh-CN.json b/packages/plugins/GoPlusSecurity/src/locales/zh-CN.json
new file mode 100644
index 000000000000..0967ef424bce
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/locales/zh-CN.json
@@ -0,0 +1 @@
+{}
diff --git a/packages/plugins/GoPlusSecurity/src/locales/zh-TW.json b/packages/plugins/GoPlusSecurity/src/locales/zh-TW.json
new file mode 100644
index 000000000000..0967ef424bce
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/locales/zh-TW.json
@@ -0,0 +1 @@
+{}
diff --git a/packages/plugins/GoPlusSecurity/src/messages.ts b/packages/plugins/GoPlusSecurity/src/messages.ts
new file mode 100644
index 000000000000..0c4bc0b6e72a
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/messages.ts
@@ -0,0 +1,13 @@
+import { createPluginMessage, PluginMessageEmitter } from '@masknet/plugin-infra'
+import { PLUGIN_ID } from './constants'
+
+type CheckSecurityDialogEvent = { open: boolean }
+
+interface PluginGoPlusSecurityMessage {
+ checkSecurityDialogEvent: CheckSecurityDialogEvent
+ rpc: unknown
+}
+
+if (import.meta.webpackHot) import.meta.webpackHot.accept()
+export const PluginGoPlusSecurityMessages: PluginMessageEmitter =
+ createPluginMessage(PLUGIN_ID)
diff --git a/packages/plugins/GoPlusSecurity/src/utils/helper.ts b/packages/plugins/GoPlusSecurity/src/utils/helper.ts
new file mode 100644
index 000000000000..39d6c594a73c
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/utils/helper.ts
@@ -0,0 +1,6 @@
+import type { ChainId } from '@masknet/web3-shared-evm'
+import urlcat from 'urlcat'
+
+export function resolveGoLabLink(chainId: ChainId, address: string) {
+ return urlcat('https://gopluslabs.io/token-security/:chainId/:address', { chainId, address })
+}
diff --git a/packages/plugins/GoPlusSecurity/tsconfig.json b/packages/plugins/GoPlusSecurity/tsconfig.json
new file mode 100644
index 000000000000..7503903514d1
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "dist",
+ "tsBuildInfoFile": "dist/.tsbuildinfo"
+ },
+ "include": ["src", "src/**/*.json"],
+ "references": [{ "path": "../../plugin-infra" }, { "path": "../../shared" }, { "path": "../../web3-providers" }]
+}
diff --git a/packages/plugins/RSS3/src/SNSAdaptor/TabCard.tsx b/packages/plugins/RSS3/src/SNSAdaptor/TabCard.tsx
index ff077de38a14..872ef9548c42 100644
--- a/packages/plugins/RSS3/src/SNSAdaptor/TabCard.tsx
+++ b/packages/plugins/RSS3/src/SNSAdaptor/TabCard.tsx
@@ -1,5 +1,6 @@
import { AddressViewer } from '@masknet/shared'
-import { AddressName, AddressNameType, EMPTY_LIST } from '@masknet/web3-shared-evm'
+import { EMPTY_LIST } from '@masknet/shared-base'
+import { AddressName, AddressNameType } from '@masknet/web3-shared-evm'
import { Box, Typography } from '@mui/material'
import { useI18N } from '../locales'
import { useDonations, useFootprints } from './hooks'
diff --git a/packages/plugins/RSS3/src/SNSAdaptor/hooks/useDonations.ts b/packages/plugins/RSS3/src/SNSAdaptor/hooks/useDonations.ts
index 39c286bd6628..faecb82d8c85 100644
--- a/packages/plugins/RSS3/src/SNSAdaptor/hooks/useDonations.ts
+++ b/packages/plugins/RSS3/src/SNSAdaptor/hooks/useDonations.ts
@@ -1,5 +1,5 @@
-import { EMPTY_LIST } from '@masknet/web3-shared-evm'
import { useAsync } from 'react-use'
+import { EMPTY_LIST } from '@masknet/shared-base'
import { PluginProfileRPC } from '../../messages'
export function useDonations(address: string) {
diff --git a/packages/plugins/Wallet/src/messages.ts b/packages/plugins/Wallet/src/messages.ts
index 7ae1e45c92d7..74d0ee0e1780 100644
--- a/packages/plugins/Wallet/src/messages.ts
+++ b/packages/plugins/Wallet/src/messages.ts
@@ -1,17 +1,16 @@
-import type BigNumber from 'bignumber.js'
-import type { JsonRpcPayload } from 'web3-core-helpers'
+import { createPluginMessage, PluginMessageEmitter } from '@masknet/plugin-infra'
import type {
- FungibleTokenDetailed,
+ ChainId,
ERC721ContractDetailed,
+ GasOption,
GasOptions,
NetworkType,
ProviderType,
TransactionState,
Wallet,
- GasOption,
- ChainId,
} from '@masknet/web3-shared-evm'
-import { createPluginMessage, PluginMessageEmitter } from '@masknet/plugin-infra'
+import type BigNumber from 'bignumber.js'
+import type { JsonRpcPayload } from 'web3-core-helpers'
import { PLUGIN_ID } from './constants'
export type TransactionDialogEvent =
@@ -102,33 +101,6 @@ export type WalletConnectQRCodeDialogEvent =
open: false
}
-export type SelectTokenDialogEvent =
- | {
- open: true
- uuid: string
- chainId?: ChainId
- disableNativeToken?: boolean
- disableSearchBar?: boolean
- FungibleTokenListProps?: {
- keyword?: string
- whitelist?: string[]
- blacklist?: string[]
- tokens?: FungibleTokenDetailed[]
- selectedTokens?: string[]
- }
- title?: string
- }
- | {
- open: false
- uuid: string
-
- /**
- * The selected detailed token.
- */
- token?: FungibleTokenDetailed
- title?: string
- }
-
export type SelectNftContractDialogEvent = {
open: boolean
uuid: string
@@ -188,11 +160,6 @@ export interface WalletMessage {
*/
gasSettingDialogUpdated: GasSettingDialogEvent
- /**
- * Select token dialog
- */
- selectTokenDialogUpdated: SelectTokenDialogEvent
-
/**
* Select nft contract dialog
*/
diff --git a/packages/plugins/tsconfig.json b/packages/plugins/tsconfig.json
index 855d124c6fa3..56c3716dd4f8 100644
--- a/packages/plugins/tsconfig.json
+++ b/packages/plugins/tsconfig.json
@@ -9,6 +9,7 @@
{ "path": "./FileService/" },
{ "path": "./RSS3/" },
{ "path": "./DAO/" },
- { "path": "./CyberConnect/" }
+ { "path": "./CyberConnect/" },
+ { "path": "./CrossChainBridge/" }
]
}
diff --git a/packages/polyfills/types/env.d.ts b/packages/polyfills/types/env.d.ts
index fdfb80edad90..fd62cb5a743f 100644
--- a/packages/polyfills/types/env.d.ts
+++ b/packages/polyfills/types/env.d.ts
@@ -4,6 +4,7 @@ declare namespace NodeJS {
}
interface ProcessEnv {
readonly NODE_ENV: 'production' | 'development'
+ readonly architecture: 'app' | 'web'
}
}
declare var process: NodeJS.Process
diff --git a/packages/public-api/src/web.ts b/packages/public-api/src/web.ts
index 4dad3ec0778a..eaebe68c57c3 100644
--- a/packages/public-api/src/web.ts
+++ b/packages/public-api/src/web.ts
@@ -177,24 +177,6 @@ export enum LaunchPage {
dashboard = 'dashboard',
}
-// This type MUST be sync with NetworkType in packages/web3-shared/src/types/index.ts
-export enum NetworkType {
- Ethereum = 'Ethereum',
- Binance = 'Binance',
- Polygon = 'Polygon',
- Arbitrum = 'Arbitrum',
- xDai = 'xDai',
- Celo = 'Celo',
- Fantom = 'Fantom',
- Aurora = 'Aurora',
- Avalanche = 'Avalanche',
- Boba = 'Boba',
- Fuse = 'Fuse',
- Metis = 'Metis',
- Optimistic = 'Optimistic',
- Conflux = 'Conflux',
-}
-
export enum DataProvider {
COIN_GECKO = 0,
COIN_MARKET_CAP = 1,
@@ -219,6 +201,7 @@ export enum TradeProvider {
PANGOLIN = 14,
MDEX = 15,
}
+
/** Supported language settings */
export enum LanguageOptions {
__auto__ = 'auto',
@@ -237,3 +220,55 @@ export enum SupportedLanguages {
koKR = 'ko-KR',
jaJP = 'ja-JP',
}
+
+/**
+ * Keep updating to packages/web3-shared/evm/types/index.ts
+ */
+export enum NetworkType {
+ Ethereum = 'Ethereum',
+ Binance = 'Binance',
+ Polygon = 'Polygon',
+ Arbitrum = 'Arbitrum',
+ xDai = 'xDai',
+ Celo = 'Celo',
+ Fantom = 'Fantom',
+ Aurora = 'Aurora',
+ Avalanche = 'Avalanche',
+ Boba = 'Boba',
+ Fuse = 'Fuse',
+ Metis = 'Metis',
+ Optimistic = 'Optimistic',
+ Conflux = 'Conflux',
+}
+
+export enum ProviderType {
+ MaskWallet = 'Maskbook',
+ MetaMask = 'MetaMask',
+ WalletConnect = 'WalletConnect',
+ Fortmatic = 'Fortmatic',
+ Coin98 = 'Coin98',
+ MathWallet = 'MathWallet',
+ WalletLink = 'WalletLink',
+ CustomNetwork = 'CustomNetwork',
+}
+
+export enum FungibleAssetProvider {
+ ZERION = 'Zerion',
+ DEBANK = 'Debank',
+}
+
+export enum NonFungibleAssetProvider {
+ OPENSEA = 'OpenSea',
+ RARIBLE = 'Rarible',
+ NFTSCAN = 'NFTScan',
+ ZORA = 'Zora',
+}
+
+export interface PriceRecord {
+ [currency: string]: number
+}
+
+/** Base on response of coingecko's token price API */
+export interface CryptoPrice {
+ [token: string]: PriceRecord
+}
diff --git a/packages/shared-base-ui/package.json b/packages/shared-base-ui/package.json
index ebb6a6817fa0..4f623f801dd5 100644
--- a/packages/shared-base-ui/package.json
+++ b/packages/shared-base-ui/package.json
@@ -8,7 +8,9 @@
"@dimensiondev/holoflows-kit": "^0.9.0-20210902104757-7c3d0d0",
"@masknet/shared-base": "workspace:*",
"react-use": "^17.3.2",
- "use-subscription": "^1.5.1",
- "uuid": "^8.3.2"
+ "use-subscription": "^1.6.0",
+ "@types/use-subscription": "^1.0.0",
+ "uuid": "^8.3.2",
+ "@types/uuid": "^8.3.4"
}
}
diff --git a/packages/shared-base-ui/src/bom/index.ts b/packages/shared-base-ui/src/bom/index.ts
new file mode 100644
index 000000000000..d2a7cce6f930
--- /dev/null
+++ b/packages/shared-base-ui/src/bom/index.ts
@@ -0,0 +1 @@
+export { openWindow } from './open-window'
diff --git a/packages/shared-base-ui/src/bom/open-window.ts b/packages/shared-base-ui/src/bom/open-window.ts
new file mode 100644
index 000000000000..cf573c57b1b6
--- /dev/null
+++ b/packages/shared-base-ui/src/bom/open-window.ts
@@ -0,0 +1,40 @@
+type WindowTarget = '_top' | '_self' | '_parent' | '_blank' | string
+
+interface BehaviorFlags {
+ popup?: boolean
+ toolbar?: boolean
+ status?: boolean
+ resizable?: boolean
+ scrollbars?: boolean
+}
+
+interface WindowFeatureFlags {
+ // Behavior
+ opener?: boolean
+ referrer?: boolean
+ behaviors?: BehaviorFlags
+ // Dimension
+ width?: number
+ height?: number
+ screenX?: number
+ screenY?: number
+}
+
+export function openWindow(
+ url: string | URL | undefined | null,
+ target: WindowTarget = '_blank',
+ features: WindowFeatureFlags = {},
+): Window | null {
+ if (!url) return null
+ const flags = []
+ for (const [name, value] of Object.entries(features.behaviors ?? {})) {
+ if (value) flags.push(`${name}=1`)
+ }
+ if (!features.opener) flags.push('noopener')
+ if (!features.referrer) flags.push('noreferrer')
+ if (Number.isFinite(features.width)) flags.push(`width=${features.width}`)
+ if (Number.isFinite(features.height)) flags.push(`height=${features.height}`)
+ if (Number.isFinite(features.screenX)) flags.push(`screenX=${features.screenX}`)
+ if (Number.isFinite(features.screenY)) flags.push(`screenY=${features.screenY}`)
+ return window.open(url, target, flags.join(' '))
+}
diff --git a/packages/shared-base-ui/src/hooks/useRemoteControlledDialog.ts b/packages/shared-base-ui/src/hooks/useRemoteControlledDialog.ts
index a7ccc57e70e5..45c62225ee24 100644
--- a/packages/shared-base-ui/src/hooks/useRemoteControlledDialog.ts
+++ b/packages/shared-base-ui/src/hooks/useRemoteControlledDialog.ts
@@ -22,7 +22,7 @@ export function useRemoteControlledDialog(
onUpdateByRemote?: (ev: T) => void,
tabType: 'self' | 'activated' = 'self',
): Result {
- const [HOOK_ID] = useState(uuid()) // create an uuid for every hook
+ const [HOOK_ID] = useState(uuid) // create an uuid for every hook
const [open, setOpen] = useState(false)
useEffect(
diff --git a/packages/shared-base-ui/src/index.ts b/packages/shared-base-ui/src/index.ts
index 7aea380be8cc..a28a3c5e7b2d 100644
--- a/packages/shared-base-ui/src/index.ts
+++ b/packages/shared-base-ui/src/index.ts
@@ -1,2 +1,3 @@
+export * from './bom'
export * from './components'
export * from './hooks'
diff --git a/packages/shared-base/package.json b/packages/shared-base/package.json
index 258140585c98..aad758c4315c 100644
--- a/packages/shared-base/package.json
+++ b/packages/shared-base/package.json
@@ -7,6 +7,7 @@
"dependencies": {
"@dimensiondev/holoflows-kit": "^0.9.0-20210902104757-7c3d0d0",
"@masknet/typed-message": "workspace:*",
+ "@masknet/public-api": "workspace:*",
"@msgpack/msgpack": "^2.7.2",
"@mui/icons-material": "link:..\\empty",
"@mui/lab": "link:..\\empty",
diff --git a/packages/shared-base/src/Messages/Mask.ts b/packages/shared-base/src/Messages/Mask.ts
index 8d158b800a07..cd28c9012b96 100644
--- a/packages/shared-base/src/Messages/Mask.ts
+++ b/packages/shared-base/src/Messages/Mask.ts
@@ -1,14 +1,16 @@
-import type { SerializableTypedMessages } from '@masknet/typed-message'
-import type { ProfileIdentifier, PersonaIdentifier } from '../Identifier/type'
-import type { RelationFavor } from '../Persona/type'
-import type { Appearance, LanguageOptions, DataProvider } from '../../../public-api/src/web'
import type {
+ Appearance,
+ LanguageOptions,
+ DataProvider,
CryptoPrice,
NetworkType,
ProviderType,
FungibleAssetProvider,
NonFungibleAssetProvider,
-} from '../../../web3-shared/evm'
+} from '@masknet/public-api'
+import type { SerializableTypedMessages } from '@masknet/typed-message'
+import type { ProfileIdentifier, PersonaIdentifier } from '../Identifier/type'
+import type { RelationFavor } from '../Persona/type'
export interface MaskSettingsEvents {
appearanceSettings: Appearance
diff --git a/packages/shared-base/src/Messages/Shared.ts b/packages/shared-base/src/Messages/Shared.ts
new file mode 100644
index 000000000000..3ad844c53345
--- /dev/null
+++ b/packages/shared-base/src/Messages/Shared.ts
@@ -0,0 +1,20 @@
+import { WebExtensionMessage } from '@dimensiondev/holoflows-kit'
+import type { CompositionRequest } from './Mask'
+
+/**
+ * @deprecated
+ * Prefer MaskMessages.
+ *
+ * Only use this in the following cases:
+ *
+ * - You need to send message across different plugins
+ * e.g. from the packages/plugins/Example to packages/plugins/Example2
+ * - You need to send message from plugin
+ * e.g. packages/plugins/Example to the main Mask extension.
+ */
+// TODO: find a way to use a good API for cross isolation communication.
+export const CrossIsolationMessages = new WebExtensionMessage({ domain: '_' })
+
+export interface CrossIsolationEvents {
+ requestComposition: CompositionRequest
+}
diff --git a/packages/shared-base/src/NextID/type.ts b/packages/shared-base/src/NextID/type.ts
index d5e4bea4334f..fa093d58324f 100644
--- a/packages/shared-base/src/NextID/type.ts
+++ b/packages/shared-base/src/NextID/type.ts
@@ -48,3 +48,11 @@ export interface NextIDBindings {
pagination: Pagination
ids: NextIDPersonaBindings[]
}
+
+// #region kv server
+export interface NextIDStoragePayload {
+ uuid: string
+ signPayload: string
+ createdAt: string
+}
+// #endregion
diff --git a/packages/mask/utils-pure/misc.ts b/packages/shared-base/src/Pure/index.ts
similarity index 100%
rename from packages/mask/utils-pure/misc.ts
rename to packages/shared-base/src/Pure/index.ts
diff --git a/packages/shared-base/src/index.ts b/packages/shared-base/src/index.ts
index ec82b1b74743..4a484473aba0 100644
--- a/packages/shared-base/src/index.ts
+++ b/packages/shared-base/src/index.ts
@@ -7,12 +7,14 @@ export * from './i18n'
export * from './utils'
export * from './kv-storage'
export * from './crypto'
+export * from './Pure'
export * from './Persona/type'
export * from './Post/type'
export * from './Persona/type'
export * from './Site/type'
export * from './Routes'
export * from './Messages/Mask'
+export * from './Messages/Shared'
export * from './Results'
export * from './convert'
export * from './NextID/type'
diff --git a/packages/shared-base/src/utils/getAssetAsBlobURL.ts b/packages/shared-base/src/utils/getAssetAsBlobURL.ts
index 78e1db5cd33d..408351af8996 100644
--- a/packages/shared-base/src/utils/getAssetAsBlobURL.ts
+++ b/packages/shared-base/src/utils/getAssetAsBlobURL.ts
@@ -3,9 +3,9 @@ const cache = new Map()
* Fetch a file and turn it into blob URL.
* This function must run in React concurrent mode.
*/
-export function getAssetAsBlobURL(url: string | URL, fetcher: (url: string) => Promise) {
+export async function getAssetAsBlobURL(url: string | URL, fetcher: (url: string) => Promise) {
url = url.toString()
- if (!cache.has(url)) throw toBlob(url, fetcher)
+ if (!cache.has(url)) return toBlob(url, fetcher)
return cache.get(url)!
}
async function toBlob(url: string, fetcher: (url: string) => Promise) {
diff --git a/packages/shared-base/src/utils/index.ts b/packages/shared-base/src/utils/index.ts
index 72ecac83030b..ebbcc29848cc 100644
--- a/packages/shared-base/src/utils/index.ts
+++ b/packages/shared-base/src/utils/index.ts
@@ -5,3 +5,4 @@ export * from './pollingTask'
export * from './sessionStorageCache'
export * from './subscription'
export * from './getAssetAsBlobURL'
+export * from './personas'
diff --git a/packages/shared/src/utils/formatter.ts b/packages/shared-base/src/utils/personas.ts
similarity index 52%
rename from packages/shared/src/utils/formatter.ts
rename to packages/shared-base/src/utils/personas.ts
index 919f1fba7940..08af447a1f79 100644
--- a/packages/shared/src/utils/formatter.ts
+++ b/packages/shared-base/src/utils/personas.ts
@@ -1,4 +1,5 @@
-export function formatFingerprint(fingerprint: string, size = 0) {
+export function formatPersonaFingerprint(fingerprint: string, size = 0) {
if (size === 0) return fingerprint
return `${fingerprint.substr(0, 2 + size)}...${fingerprint.substr(-size)}`
}
+export const MAX_PERSONA_LIMIT = 10
diff --git a/packages/shared-base/src/utils/requestComposition.ts b/packages/shared-base/src/utils/requestComposition.ts
new file mode 100644
index 000000000000..4fa125750129
--- /dev/null
+++ b/packages/shared-base/src/utils/requestComposition.ts
@@ -0,0 +1,10 @@
+import { CrossIsolationMessages } from '@masknet/shared-base'
+export function requestComposition(startupPlugin: string) {
+ CrossIsolationMessages.events.requestComposition.sendToLocal({
+ reason: 'timeline',
+ open: true,
+ options: {
+ startupPlugin,
+ },
+ })
+}
diff --git a/packages/shared-base/tsconfig.json b/packages/shared-base/tsconfig.json
index 398b55ddd11e..21eef249d5b7 100644
--- a/packages/shared-base/tsconfig.json
+++ b/packages/shared-base/tsconfig.json
@@ -7,5 +7,5 @@
},
"include": ["./src", "./src/**/*.json"],
// Only type-level dependency.
- "references": [{ "path": "../public-api" }, { "path": "../web3-shared/evm" }, { "path": "../typed-message/base" }]
+ "references": [{ "path": "../public-api" }, { "path": "../typed-message/base" }]
}
diff --git a/packages/shared/package.json b/packages/shared/package.json
index f2a4402709ae..2a66c2dda832 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -5,6 +5,7 @@
"dependencies": {
"@dimensiondev/holoflows-kit": "^0.9.0-20210902104757-7c3d0d0",
"@masknet/icons": "workspace:*",
+ "@masknet/shared-base-ui": "workspace:*",
"@masknet/plugin-infra": "workspace:*",
"@masknet/shared-base": "workspace:*",
"@masknet/theme": "workspace:*",
@@ -19,7 +20,6 @@
"react-feather": "^2.0.9",
"react-use": "^17.3.2",
"urlcat": "^2.0.4",
- "use-subscription": "^1.5.1",
- "uuid": "^8.3.2"
+ "use-subscription": "^1.6.0"
}
}
diff --git a/packages/shared/src/UI/components/ApplicationEntry/index.tsx b/packages/shared/src/UI/components/ApplicationEntry/index.tsx
new file mode 100644
index 000000000000..206536e6966f
--- /dev/null
+++ b/packages/shared/src/UI/components/ApplicationEntry/index.tsx
@@ -0,0 +1,58 @@
+import classNames from 'classnames'
+import { makeStyles } from '@masknet/theme'
+import { Typography } from '@mui/material'
+
+const useStyles = makeStyles()((theme) => ({
+ applicationBox: {
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: theme.palette.background.default,
+ borderRadius: '8px',
+
+ height: 100,
+ },
+ applicationBoxHover: {
+ cursor: 'pointer',
+ '&:hover': {
+ transform: 'scale(1.05) translateY(-4px)',
+ boxShadow: theme.palette.mode === 'light' ? '0px 10px 16px rgba(0, 0, 0, 0.1)' : 'none',
+ },
+ },
+ applicationImg: {
+ width: 36,
+ height: 36,
+ marginBottom: 10,
+ },
+ title: {
+ fontSize: 15,
+ },
+ disabled: {
+ opacity: 0.4,
+ cursor: 'default',
+ pointerEvent: 'none',
+ },
+}))
+
+interface Props {
+ icon: string
+ title: string
+ disabled?: boolean
+ onClick: () => void
+}
+
+export function ApplicationEntry(props: Props) {
+ const { icon, title, onClick, disabled = false } = props
+ const { classes } = useStyles()
+ return (
+ {} : onClick}>
+
+
+ {title}
+
+
+ )
+}
diff --git a/packages/shared/src/UI/components/AssetPlayer/index.tsx b/packages/shared/src/UI/components/AssetPlayer/index.tsx
index 08ce050d74b1..ed4b076351a1 100644
--- a/packages/shared/src/UI/components/AssetPlayer/index.tsx
+++ b/packages/shared/src/UI/components/AssetPlayer/index.tsx
@@ -163,10 +163,7 @@ export const AssetPlayer = memo((props) => {
setIframe()
}}
className={
- ![
- AssetPlayerState.NORMAL,
- ...(props.showIframeFromInit ? [AssetPlayerState.INIT] : []),
- ].includes(playerState)
+ ![AssetPlayerState.NORMAL, AssetPlayerState.INIT].includes(playerState)
? classes.hidden
: classes.iframe
}
diff --git a/packages/shared/src/UI/components/ImageIcon/index.tsx b/packages/shared/src/UI/components/ImageIcon/index.tsx
index 90110f420a1d..5a020afcee7b 100644
--- a/packages/shared/src/UI/components/ImageIcon/index.tsx
+++ b/packages/shared/src/UI/components/ImageIcon/index.tsx
@@ -8,7 +8,7 @@ const useStyles = makeStyles()((theme) => {
export interface ImageIconProps extends withClasses<'icon'> {
size?: number
- icon?: URL
+ icon?: URL | string
}
export function ImageIcon(props: ImageIconProps) {
diff --git a/packages/shared/src/UI/components/NFTCardStyledAssetPlayer/index.tsx b/packages/shared/src/UI/components/NFTCardStyledAssetPlayer/index.tsx
index 2aeb6adc0757..ac2bea3ed059 100644
--- a/packages/shared/src/UI/components/NFTCardStyledAssetPlayer/index.tsx
+++ b/packages/shared/src/UI/components/NFTCardStyledAssetPlayer/index.tsx
@@ -13,9 +13,6 @@ const useStyles = makeStyles()((theme) => ({
width: 120,
overflow: 'hidden',
},
- loadingNftImg: {
- marginTop: 20,
- },
loadingPlaceholder: {
height: 160,
width: 120,
@@ -45,6 +42,10 @@ interface Props extends withClasses<'loadingFailImage' | 'iframe' | 'wrapper' |
setERC721TokenName?: (name: string) => void
setSourceType?: (type: string) => void
}
+
+const assetPlayerFallbackImageDark = new URL('./nft_token_fallback_dark.png', import.meta.url)
+const assetPlayerFallbackImageLight = new URL('./nft_token_fallback.png', import.meta.url)
+
export function NFTCardStyledAssetPlayer(props: Props) {
const {
chainId = ChainId.Mainnet,
@@ -67,9 +68,7 @@ export function NFTCardStyledAssetPlayer(props: Props) {
const { value: isImageToken } = useImageChecker(url || tokenDetailed?.info.imageURL || tokenDetailed?.info.mediaUrl)
const fallbackImageURL =
- theme.palette.mode === 'dark'
- ? new URL('./nft_token_fallback_dark.png', import.meta.url)
- : new URL('./nft_token_fallback.png', import.meta.url)
+ theme.palette.mode === 'dark' ? assetPlayerFallbackImageDark : assetPlayerFallbackImageLight
return isImageToken || isNative ? (
@@ -86,6 +85,7 @@ export function NFTCardStyledAssetPlayer(props: Props) {
) : (
200) iframe at once.
renderTimeout={renderOrder ? 20000 * Math.floor(renderOrder / 100) : undefined}
fallbackImage={fallbackImage ?? fallbackImageURL}
- loadingIcon={ }
+ loadingIcon={ }
classes={{
iframe: classNames(classes.wrapper, classes.iframe),
errorPlaceholder: classes.wrapper,
diff --git a/packages/shared/src/UI/components/index.ts b/packages/shared/src/UI/components/index.ts
index b4d86259c80c..b32738abcf4a 100644
--- a/packages/shared/src/UI/components/index.ts
+++ b/packages/shared/src/UI/components/index.ts
@@ -13,3 +13,4 @@ export * from './I18NextProviderHMR'
export * from './AssetPlayer'
export * from './NFTCardStyledAssetPlayer'
export * from './ReversedAddress'
+export * from './ApplicationEntry'
diff --git a/packages/shared/src/constants.tsx b/packages/shared/src/constants.tsx
index b11fc21698e8..03e0e05d3d99 100644
--- a/packages/shared/src/constants.tsx
+++ b/packages/shared/src/constants.tsx
@@ -7,15 +7,20 @@ import {
OpenSeaColoredIcon,
} from '@masknet/icons'
+export const MINDS_ID = 'minds.com'
+export const FACEBOOK_ID = 'facebook.com'
+export const TWITTER_ID = 'twitter.com'
+export const INSTAGRAM_ID = 'instagram.com'
+export const OPENSEA_ID = 'opensea.io'
+
export const SOCIAL_MEDIA_ICON_MAPPING: Record = {
- 'twitter.com': ,
- 'facebook.com': ,
- 'minds.com': ,
- 'instagram.com': ,
- 'opensea.io': ,
+ [TWITTER_ID]: ,
+ [FACEBOOK_ID]: ,
+ [MINDS_ID]: ,
+ [INSTAGRAM_ID]: ,
+ [OPENSEA_ID]: ,
}
export const mediaViewerUrl = 'https://media-viewer.r2d2.to/index.html'
export const MAX_WALLET_LIMIT = 100
-export const MAX_PERSONA_LIMIT = 10
diff --git a/packages/shared/src/contexts/SharedContextProvider.tsx b/packages/shared/src/contexts/SharedContextProvider.tsx
new file mode 100644
index 000000000000..4191366944a1
--- /dev/null
+++ b/packages/shared/src/contexts/SharedContextProvider.tsx
@@ -0,0 +1,11 @@
+import type { FC } from 'react'
+import { BaseSharedUIProvider } from './base'
+import { EvmSharedUIProvider } from './evm'
+
+export const SharedContextProvider: FC = ({ children }) => {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/packages/shared/src/contexts/base/BaseSharedUIProvider.tsx b/packages/shared/src/contexts/base/BaseSharedUIProvider.tsx
new file mode 100644
index 000000000000..bcaee9b4979b
--- /dev/null
+++ b/packages/shared/src/contexts/base/BaseSharedUIProvider.tsx
@@ -0,0 +1,36 @@
+import { ValueRef } from '@dimensiondev/holoflows-kit'
+import { createContext, FC, useContext, useMemo } from 'react'
+import { useValueRef } from '@masknet/shared-base-ui'
+import type { SharedComponentOverwrite } from './types'
+
+export const sharedUINetworkIdentifier = new ValueRef('unknown')
+export const sharedUIComponentOverwrite = new ValueRef({})
+
+interface ContextOptions {
+ networkIdentifier: string
+ componentOverwrite: SharedComponentOverwrite
+}
+
+const BaseUIContext = createContext({
+ networkIdentifier: sharedUINetworkIdentifier.value,
+ componentOverwrite: sharedUIComponentOverwrite.value,
+})
+
+export const BaseSharedUIProvider: FC = ({ children }) => {
+ const snsId = useValueRef(sharedUINetworkIdentifier)
+ const overwrite = useValueRef(sharedUIComponentOverwrite)
+
+ const contextValue = useMemo(() => {
+ const value: ContextOptions = {
+ networkIdentifier: snsId,
+ componentOverwrite: overwrite,
+ }
+ return value
+ }, [snsId, overwrite])
+
+ return {children}
+}
+
+export const useBaseUIRuntime = () => {
+ return useContext(BaseUIContext)
+}
diff --git a/packages/shared/src/contexts/base/index.ts b/packages/shared/src/contexts/base/index.ts
new file mode 100644
index 000000000000..3ad8144c494a
--- /dev/null
+++ b/packages/shared/src/contexts/base/index.ts
@@ -0,0 +1,2 @@
+export * from './BaseSharedUIProvider'
+export * from './types'
diff --git a/packages/shared/src/contexts/base/types.ts b/packages/shared/src/contexts/base/types.ts
new file mode 100644
index 000000000000..04c1876e526a
--- /dev/null
+++ b/packages/shared/src/contexts/base/types.ts
@@ -0,0 +1,6 @@
+import type { ComponentOverwriteConfig } from '@masknet/theme'
+import type { InjectedDialogClassKey, InjectedDialogProps } from '../components'
+
+export interface SharedComponentOverwrite {
+ InjectedDialog?: ComponentOverwriteConfig
+}
diff --git a/packages/shared/src/contexts/components/DialogDismissIcon.tsx b/packages/shared/src/contexts/components/DialogDismissIcon.tsx
new file mode 100644
index 000000000000..b6b0af2ddd4c
--- /dev/null
+++ b/packages/shared/src/contexts/components/DialogDismissIcon.tsx
@@ -0,0 +1,20 @@
+// see https://github.com/import-js/eslint-plugin-import/issues/2288
+// eslint-disable-next-line import/no-deprecated
+import { useTheme, useMediaQuery } from '@mui/material'
+import CloseIcon from '@mui/icons-material/Close'
+import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded'
+
+export interface DialogDismissIconUIProps {
+ style?: 'auto' | 'back' | 'close'
+}
+
+export function DialogDismissIcon(props: DialogDismissIconUIProps) {
+ const close =
+ const back =
+ // see https://github.com/import-js/eslint-plugin-import/issues/2288
+ // eslint-disable-next-line import/no-deprecated
+ const auto = useMediaQuery(`(min-width: ${useTheme().breakpoints.values.sm}px)`)
+ if (!props.style || props.style === 'auto') return auto ? close : back
+ if (props.style === 'back') return back
+ return close
+}
diff --git a/packages/mask/src/components/shared/InjectedDialog.tsx b/packages/shared/src/contexts/components/InjectedDialog.tsx
similarity index 79%
rename from packages/mask/src/components/shared/InjectedDialog.tsx
rename to packages/shared/src/contexts/components/InjectedDialog.tsx
index c8275949d509..99355b9cac55 100644
--- a/packages/mask/src/components/shared/InjectedDialog.tsx
+++ b/packages/shared/src/contexts/components/InjectedDialog.tsx
@@ -1,5 +1,8 @@
-import { Children, cloneElement } from 'react'
+import { EnhanceableSite, isDashboardPage } from '@masknet/shared-base'
+import { ErrorBoundary, useValueRef } from '@masknet/shared-base-ui'
+import { makeStyles, mergeClasses, useDialogStackActor, usePortalShadowRoot, useStylesExtends } from '@masknet/theme'
import {
+ Dialog,
DialogActions,
DialogClassKey,
DialogContent,
@@ -8,22 +11,19 @@ import {
DialogTitle,
IconButton,
Typography,
- useTheme,
- Dialog,
useMediaQuery,
+ useTheme,
} from '@mui/material'
-import { makeStyles, useDialogStackActor, useStylesExtends, mergeClasses } from '@masknet/theme'
-import { EnhanceableSite, isDashboardPage } from '@masknet/shared-base'
-import { ErrorBoundary } from '@masknet/shared-base-ui'
-import { useI18N, usePortalShadowRoot } from '../../utils'
-import { DialogDismissIconUI } from '../InjectedComponents/DialogDismissIcon'
-import { activatedSocialNetworkUI } from '../../social-network'
+import { Children, cloneElement } from 'react'
+import { useSharedI18N } from '../../locales'
+import { sharedUIComponentOverwrite, sharedUINetworkIdentifier } from '../base'
+import { DialogDismissIcon } from './DialogDismissIcon'
interface StyleProps {
- snsId: string
+ clean: boolean
}
-const useStyles = makeStyles()((theme, { snsId }) => ({
+const useStyles = makeStyles()((theme, { clean }) => ({
dialogTitle: {
padding: theme.spacing(1, 2),
borderBottom: `1px solid ${theme.palette.divider}`,
@@ -38,11 +38,7 @@ const useStyles = makeStyles()((theme, { snsId }) => ({
dialogCloseButton: {
color: theme.palette.text.primary,
},
- paper: {
- ...(snsId === EnhanceableSite.Minds || snsId === EnhanceableSite.Facebook
- ? { width: 'auto', backgroundImage: 'none' }
- : {}),
- },
+ paper: clean ? { width: 'auto', backgroundImage: 'none' } : {},
}))
export type InjectedDialogClassKey =
@@ -58,14 +54,17 @@ export interface InjectedDialogProps extends Omit>
onClose?(): void
title?: React.ReactChild
+ titleTail?: React.ReactChild | null
disableBackdropClick?: boolean
disableTitleBorder?: boolean
titleBarIconStyle?: 'auto' | 'back' | 'close'
}
export function InjectedDialog(props: InjectedDialogProps) {
- const overwrite = activatedSocialNetworkUI.customization.componentOverwrite || {}
+ const snsId = useValueRef(sharedUINetworkIdentifier)
+ const overwrite = useValueRef(sharedUIComponentOverwrite)
props = overwrite.InjectedDialog?.props?.(props) ?? props
+ const clean = snsId === EnhanceableSite.Minds || snsId === EnhanceableSite.Facebook
const {
dialogActions,
dialogCloseButton,
@@ -75,16 +74,22 @@ export function InjectedDialog(props: InjectedDialogProps) {
dialogBackdropRoot,
container,
...dialogClasses
- } = useStylesExtends(
- useStyles({ snsId: activatedSocialNetworkUI.networkIdentifier }),
- props,
- overwrite.InjectedDialog?.classes,
- )
+ } = useStylesExtends(useStyles({ clean }), props, overwrite.InjectedDialog?.classes)
+
+ const t = useSharedI18N()
const fullScreen = useMediaQuery(useTheme().breakpoints.down('xs'))
const isDashboard = isDashboardPage()
- const { children, open, disableBackdropClick, titleBarIconStyle, onClose, title, disableTitleBorder, ...rest } =
- props
- const { t } = useI18N()
+ const {
+ children,
+ open,
+ disableBackdropClick,
+ titleBarIconStyle,
+ onClose,
+ title,
+ titleTail = null,
+ disableTitleBorder,
+ ...rest
+ } = props
const actions = CopyElementWithNewProps(children, DialogActions, { root: dialogActions })
const content = CopyElementWithNewProps(children, DialogContent, { root: dialogContent })
const { extraProps, shouldReplaceExitWithBack, IncreaseStack } = useDialogStackActor(open)
@@ -124,15 +129,16 @@ export function InjectedDialog(props: InjectedDialogProps) {
-
{title}
+ {titleTail}
) : null}
{/* There is a .MuiDialogTitle+.MuiDialogContent selector that provides paddingTop: 0 */}
diff --git a/packages/shared/src/contexts/components/index.ts b/packages/shared/src/contexts/components/index.ts
new file mode 100644
index 000000000000..beb73912eb49
--- /dev/null
+++ b/packages/shared/src/contexts/components/index.ts
@@ -0,0 +1 @@
+export * from './InjectedDialog'
diff --git a/packages/shared/src/contexts/evm/TokenPicker/SelectTokenDialog.tsx b/packages/shared/src/contexts/evm/TokenPicker/SelectTokenDialog.tsx
new file mode 100644
index 000000000000..37c841bc85e3
--- /dev/null
+++ b/packages/shared/src/contexts/evm/TokenPicker/SelectTokenDialog.tsx
@@ -0,0 +1,110 @@
+import { EMPTY_LIST } from '@masknet/shared-base'
+import { ERC20TokenList, useSharedI18N } from '@masknet/shared'
+import { makeStyles, MaskColorVar } from '@masknet/theme'
+import { ChainId, FungibleTokenDetailed, useTokenConstants } from '@masknet/web3-shared-evm'
+// see https://github.com/import-js/eslint-plugin-import/issues/2288
+// eslint-disable-next-line import/no-deprecated
+import { DialogContent, Theme, useMediaQuery } from '@mui/material'
+import type { FC } from 'react'
+import { useBaseUIRuntime } from '../../base'
+import { MINDS_ID } from '../../../constants'
+import { InjectedDialog } from '../../components'
+import { useRowSize } from './useRowSize'
+
+interface StyleProps {
+ compact: boolean
+ disablePaddingTop: boolean
+}
+
+const useStyles = makeStyles()((theme, { compact, disablePaddingTop }) => ({
+ content: {
+ ...(compact ? { minWidth: 552 } : {}),
+ padding: theme.spacing(3),
+ paddingTop: disablePaddingTop ? 0 : theme.spacing(2.8),
+ },
+ list: {
+ scrollbarWidth: 'none',
+ '&::-webkit-scrollbar': {
+ display: 'none',
+ },
+ },
+ placeholder: {
+ textAlign: 'center',
+ height: 288,
+ paddingTop: theme.spacing(14),
+ boxSizing: 'border-box',
+ },
+ search: {
+ backgroundColor: 'transparent !important',
+ border: `solid 1px ${MaskColorVar.twitterBorderLine}`,
+ },
+}))
+
+export interface PickTokenOptions {
+ disableNativeToken?: boolean
+ chainId?: ChainId
+ disableSearchBar?: boolean
+ keyword?: string
+ whitelist?: string[]
+ title?: string
+ blacklist?: string[]
+ tokens?: FungibleTokenDetailed[]
+ selectedTokens?: string[]
+}
+
+export interface SelectTokenDialogProps extends PickTokenOptions {
+ open: boolean
+ onSelect?(token: FungibleTokenDetailed): void
+ onClose?(): void
+}
+
+export const SelectTokenDialog: FC = ({
+ open,
+ chainId,
+ disableSearchBar,
+ disableNativeToken,
+ tokens,
+ blacklist = EMPTY_LIST,
+ selectedTokens = EMPTY_LIST,
+ onSelect,
+ onClose,
+ title,
+}) => {
+ const t = useSharedI18N()
+ const isDashboard = location.href.includes('dashboard.html')
+ const { networkIdentifier } = useBaseUIRuntime()
+ const compact = networkIdentifier === MINDS_ID
+ const { classes } = useStyles({ compact, disablePaddingTop: isDashboard })
+ const { NATIVE_TOKEN_ADDRESS } = useTokenConstants(chainId)
+ // eslint-disable-next-line import/no-deprecated
+ const isMdScreen = useMediaQuery((theme) => theme.breakpoints.down('md'))
+
+ const rowSize = useRowSize()
+
+ return (
+
+
+
+
+
+ )
+}
diff --git a/packages/shared/src/contexts/evm/TokenPicker/index.tsx b/packages/shared/src/contexts/evm/TokenPicker/index.tsx
new file mode 100644
index 000000000000..7751c4be66ee
--- /dev/null
+++ b/packages/shared/src/contexts/evm/TokenPicker/index.tsx
@@ -0,0 +1,66 @@
+import { defer, DeferTuple } from '@dimensiondev/kit'
+import { EMPTY_LIST } from '@masknet/shared-base'
+import type { FungibleTokenDetailed } from '@masknet/web3-shared-evm'
+import { createContext, FC, useCallback, useContext, useMemo, useState } from 'react'
+import { PickTokenOptions, SelectTokenDialog } from './SelectTokenDialog'
+
+interface ContextOptions {
+ pickToken: (options: PickTokenOptions) => Promise
+}
+const TokenPickerContext = createContext(null!)
+
+type PickerDeferTuple = DeferTuple
+
+interface Task {
+ id: number
+ promise: PickerDeferTuple[0]
+ resolve: PickerDeferTuple[1]
+ reject: PickerDeferTuple[2]
+ pickerOptions: PickTokenOptions
+}
+
+let id = 0
+export const TokenPickerProvider: FC = ({ children }) => {
+ const [tasks, setTasks] = useState(EMPTY_LIST)
+
+ const removeTask = useCallback((task: Task) => {
+ setTasks((list) => list.filter((t) => t !== task))
+ }, [])
+
+ const contextValue = useMemo(() => {
+ return {
+ pickToken: (options: PickTokenOptions) => {
+ const [promise, resolve, reject] = defer()
+ id += 1
+ const newTask: Task = { id, promise, resolve, reject, pickerOptions: options }
+ setTasks((list) => [...list, newTask])
+ return promise
+ },
+ }
+ }, [])
+
+ return (
+
+ {children}
+ {tasks.map((task) => (
+ {
+ task.resolve(token)
+ removeTask(task)
+ }}
+ onClose={() => {
+ task.resolve(null)
+ removeTask(task)
+ }}
+ />
+ ))}
+
+ )
+}
+
+export const usePickToken = () => {
+ return useContext(TokenPickerContext).pickToken
+}
diff --git a/packages/shared/src/contexts/evm/TokenPicker/useRowSize.ts b/packages/shared/src/contexts/evm/TokenPicker/useRowSize.ts
new file mode 100644
index 000000000000..59a8a05c5b5e
--- /dev/null
+++ b/packages/shared/src/contexts/evm/TokenPicker/useRowSize.ts
@@ -0,0 +1,15 @@
+import { useEffect, useState } from 'react'
+
+export function useRowSize() {
+ const [rowSize, setRowSize] = useState(54)
+
+ useEffect(() => {
+ try {
+ const fontSize = Number.parseFloat(getComputedStyle(document.documentElement).fontSize)
+ setRowSize(fontSize * 4)
+ } catch {
+ setRowSize(60)
+ }
+ }, [])
+ return rowSize
+}
diff --git a/packages/shared/src/contexts/evm/index.tsx b/packages/shared/src/contexts/evm/index.tsx
new file mode 100644
index 000000000000..6f1f934b17e0
--- /dev/null
+++ b/packages/shared/src/contexts/evm/index.tsx
@@ -0,0 +1,8 @@
+import type { FC } from 'react'
+import { TokenPickerProvider } from './TokenPicker'
+
+export * from './TokenPicker'
+
+export const EvmSharedUIProvider: FC = ({ children }) => {
+ return {children}
+}
diff --git a/packages/shared/src/contexts/index.ts b/packages/shared/src/contexts/index.ts
new file mode 100644
index 000000000000..f56942ef31c8
--- /dev/null
+++ b/packages/shared/src/contexts/index.ts
@@ -0,0 +1,4 @@
+export * from './evm'
+export * from './base'
+export * from './components'
+export * from './SharedContextProvider'
diff --git a/packages/shared/src/hooks/useMenu.tsx b/packages/shared/src/hooks/useMenu.tsx
index d23fe86f93be..93ae72f84a24 100644
--- a/packages/shared/src/hooks/useMenu.tsx
+++ b/packages/shared/src/hooks/useMenu.tsx
@@ -4,6 +4,7 @@ import { useUpdate } from 'react-use'
/** Provide ShadowRootMenu for useMenu in content script. */
export const useMenuContext = createContext>(Menu)
+
/**
* A util hooks for easier to use ``s.
*
@@ -15,6 +16,7 @@ export function useMenu(...elements: Array) {
}
export interface useMenuConfig extends Partial {}
+
export function useMenuConfig(
elements: Array,
config: useMenuConfig,
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index a552a39b2942..91e163b62985 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -1,9 +1,9 @@
+export * from './contexts'
export * from './hooks'
export * from './wallet'
export * from './UI'
export * from './locales'
export * from './locales/languages'
-export * from './utils'
export * from './constants'
// This interface is used as a proxy type to avoid circular project dependencies
export interface DashboardPluginMessages {
diff --git a/packages/shared/src/locales/en-US.json b/packages/shared/src/locales/en-US.json
index 11514454ef72..134f72c9fc51 100644
--- a/packages/shared/src/locales/en-US.json
+++ b/packages/shared/src/locales/en-US.json
@@ -19,5 +19,6 @@
"address_viewer_address_name_dns": "DNS",
"address_viewer_address_name_rns": "RNS",
"address_viewer_address_name_address": "address",
- "address_viewer_address_name_twitter": "twitter blue"
+ "address_viewer_address_name_twitter": "twitter blue",
+ "dialog_dismiss": "Dismiss"
}
diff --git a/packages/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts
deleted file mode 100644
index 80c86669067a..000000000000
--- a/packages/shared/src/utils/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './formatter'
diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json
index 49d1a5a6cdf3..b3e26abdc7e2 100644
--- a/packages/shared/tsconfig.json
+++ b/packages/shared/tsconfig.json
@@ -9,6 +9,7 @@
"references": [
{ "path": "../web3-shared/evm" },
{ "path": "../theme" },
+ { "path": "../shared-base-ui" },
{ "path": "../shared-base" },
{ "path": "../shared-base-ui" },
{ "path": "../icons" },
diff --git a/packages/theme/package.json b/packages/theme/package.json
index a924008c6ee2..d0828f1839d8 100644
--- a/packages/theme/package.json
+++ b/packages/theme/package.json
@@ -22,6 +22,7 @@
},
"dependencies": {
"@masknet/icons": "workspace:*",
+ "@masknet/shared-base": "workspace:*",
"@masknet/storybook-shared": "workspace:*",
"@types/qrcode": "^1.4.2",
"@types/tinycolor2": "^1.4.3",
diff --git a/packages/theme/src/Components/Dialogs/Dialog.tsx b/packages/theme/src/Components/Dialogs/Dialog.tsx
index b709269a18e4..2821bf95166c 100644
--- a/packages/theme/src/Components/Dialogs/Dialog.tsx
+++ b/packages/theme/src/Components/Dialogs/Dialog.tsx
@@ -41,8 +41,8 @@ export const MaskDialog = memo((props: MaskDialogProps) => {
})
export function useMaskDialog(title: string, content: ReactNode, actions: ReactNode) {
- const [isOpen, open] = useState(false)
- const onClose = useCallback(() => open(false), [])
+ const [isOpen, setOpen] = useState(false)
+ const onClose = useCallback(() => setOpen(false), [])
return (
{content}
diff --git a/packages/theme/src/Components/Snackbar/index.tsx b/packages/theme/src/Components/Snackbar/index.tsx
index 2b3a8b40a9d3..c2820d301b4d 100644
--- a/packages/theme/src/Components/Snackbar/index.tsx
+++ b/packages/theme/src/Components/Snackbar/index.tsx
@@ -104,7 +104,7 @@ to {
return {
root: {
zIndex: 9999,
- transform: typeof offsetY !== undefined ? `translateY(${80}px)` : 'none',
+ transform: typeof offsetY !== undefined ? `translateY(${offsetY}px)` : 'none',
color: MaskColorVar.textLight,
pointerEvents: 'inherit',
},
diff --git a/packages/theme/src/ShadowRoot/createReactRootShadowed.tsx b/packages/theme/src/ShadowRoot/createReactRootShadowed.tsx
index a76abe77a4f2..583c98b523f4 100644
--- a/packages/theme/src/ShadowRoot/createReactRootShadowed.tsx
+++ b/packages/theme/src/ShadowRoot/createReactRootShadowed.tsx
@@ -1,5 +1,6 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
+import { noop } from 'lodash-unified'
import type {} from 'react/next'
import type {} from 'react-dom/next'
import { ShadowRootStyleProvider } from './ShadowRootStyleProvider'
@@ -68,8 +69,8 @@ function mount(
if (shadow.querySelector(`${tag}.${key}`)) {
console.error('Tried to create root in', shadow, 'with key', key, ' which is already used. Skip rendering.')
return {
- destory: () => {},
- render: () => {},
+ destory: noop,
+ render: noop,
}
}
diff --git a/packages/theme/src/customization/index.ts b/packages/theme/src/customization/index.ts
new file mode 100644
index 000000000000..c9f6f047dc00
--- /dev/null
+++ b/packages/theme/src/customization/index.ts
@@ -0,0 +1 @@
+export * from './types'
diff --git a/packages/theme/src/customization/types.ts b/packages/theme/src/customization/types.ts
new file mode 100644
index 000000000000..82b7ebeaf57e
--- /dev/null
+++ b/packages/theme/src/customization/types.ts
@@ -0,0 +1,6 @@
+type ClassNameMap = { [P in ClassKey]: string }
+
+export interface ComponentOverwriteConfig {
+ classes?: () => { classes: Partial> }
+ props?: (props: Props) => Props
+}
diff --git a/packages/theme/src/index.ts b/packages/theme/src/index.ts
index a51f9b24b3e3..033c66de2224 100644
--- a/packages/theme/src/index.ts
+++ b/packages/theme/src/index.ts
@@ -40,6 +40,7 @@ export const MaskDarkTheme = MaskTheme('dark')
export * from './makeStyles'
export * from './Components'
export * from './hooks'
+export * from './customization'
export * from './ShadowRoot'
export * from './UIHelper/custom-ui-helper'
export * from './CSSVariableInjector'
diff --git a/packages/theme/tsconfig.json b/packages/theme/tsconfig.json
index bb4139031a1a..174fe5764c22 100644
--- a/packages/theme/tsconfig.json
+++ b/packages/theme/tsconfig.json
@@ -6,5 +6,5 @@
"tsBuildInfoFile": "./dist/src.tsbuildinfo"
},
"include": ["./src"],
- "references": [{ "path": "../icons" }]
+ "references": [{ "path": "../icons" }, { "path": "../shared-base" }]
}
diff --git a/packages/web3-constants/compile-constants.ts b/packages/web3-constants/compile-constants.ts
index cafdb8aa29af..e9462b36f726 100644
--- a/packages/web3-constants/compile-constants.ts
+++ b/packages/web3-constants/compile-constants.ts
@@ -59,6 +59,7 @@ compileConstants(path.join(__dirname, 'evm'), [
'Fantom',
'Aurora',
'Aurora_Testnet',
+ 'Conflux',
])
compileConstants(path.join(__dirname, 'solana'), ['Mainnet', 'Testnet', 'Devnet'])
diff --git a/packages/web3-constants/evm/airdrop.json b/packages/web3-constants/evm/airdrop.json
index ab949267d4f5..28ca1f00abf1 100644
--- a/packages/web3-constants/evm/airdrop.json
+++ b/packages/web3-constants/evm/airdrop.json
@@ -17,6 +17,7 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/artblocks.json b/packages/web3-constants/evm/artblocks.json
index b626e1dae9f6..0ac17edfdcae 100644
--- a/packages/web3-constants/evm/artblocks.json
+++ b/packages/web3-constants/evm/artblocks.json
@@ -17,6 +17,7 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/cryptoartai.json b/packages/web3-constants/evm/cryptoartai.json
index edde7f04abdc..8819260601ef 100644
--- a/packages/web3-constants/evm/cryptoartai.json
+++ b/packages/web3-constants/evm/cryptoartai.json
@@ -17,7 +17,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"ARTIST_ACCEPTING_BIDS_V2": {
"Mainnet": "0x78C889749f29D2965a76Ede3BBb232A9729Ccf0b",
@@ -37,7 +38,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"CANFT_MARKET": {
"Mainnet": "0x72d081953957723e540780a0C6bA31725469238E",
@@ -57,6 +59,7 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/dhedge.json b/packages/web3-constants/evm/dhedge.json
index 870f99083e7f..9aa7b2dbd671 100644
--- a/packages/web3-constants/evm/dhedge.json
+++ b/packages/web3-constants/evm/dhedge.json
@@ -17,7 +17,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"API_URL": {
"Mainnet": "https://api-v2.dhedge.org/graphql",
@@ -37,6 +38,7 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/ethereum.json b/packages/web3-constants/evm/ethereum.json
index 4e4fefb89d3b..9915418599d4 100644
--- a/packages/web3-constants/evm/ethereum.json
+++ b/packages/web3-constants/evm/ethereum.json
@@ -59,7 +59,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"ENS_REVERSE_RECORDS_ADDRESS": {
"Mainnet": "0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C",
@@ -79,6 +80,7 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/explorer.json b/packages/web3-constants/evm/explorer.json
index a3c5bf0d117e..55f6e2a86fc2 100644
--- a/packages/web3-constants/evm/explorer.json
+++ b/packages/web3-constants/evm/explorer.json
@@ -17,7 +17,8 @@
"Celo": [],
"Fantom": ["AST4WWPNEYDURUXG2GH32JZMYWEFDP999S"],
"Aurora": [],
- "Aurora_Testnet": []
+ "Aurora_Testnet": [],
+ "Conflux": []
},
"EXPLORER_API": {
"Mainnet": "https://api.etherscan.io/api",
@@ -37,6 +38,7 @@
"Celo": "https://explorer.celo.org/api",
"Fantom": "https://api.ftmscan.com/api",
"Aurora": "https://explorer.mainnet.aurora.dev/api",
- "Aurora_Testnet": "https://explorer.testnet.aurora.dev/api"
+ "Aurora_Testnet": "https://explorer.testnet.aurora.dev/api",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/gitcoin.json b/packages/web3-constants/evm/gitcoin.json
index 708e296ea6de..c26772f90dd9 100644
--- a/packages/web3-constants/evm/gitcoin.json
+++ b/packages/web3-constants/evm/gitcoin.json
@@ -17,7 +17,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"BULK_CHECKOUT_ADDRESS": {
"Mainnet": "0x7d655c57f71464B6f83811C55D84009Cd9f5221C",
@@ -37,7 +38,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"GITCOIN_ETH_ADDRESS": {
"Mainnet": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
@@ -57,7 +59,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"GITCOIN_TIP_PERCENTAGE": {
"Mainnet": 5,
@@ -77,6 +80,7 @@
"Celo": 0,
"Fantom": 0,
"Aurora": 0,
- "Aurora_Testnet": 0
+ "Aurora_Testnet": 0,
+ "Conflux": 0
}
}
diff --git a/packages/web3-constants/evm/good-ghosting.json b/packages/web3-constants/evm/good-ghosting.json
index e54f1e6ba1bf..9ab7b3a1ba84 100644
--- a/packages/web3-constants/evm/good-ghosting.json
+++ b/packages/web3-constants/evm/good-ghosting.json
@@ -17,7 +17,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"GOOD_GHOSTING_INCENTIVES_CONTRACT_ADDRESS": {
"Mainnet": "",
@@ -37,6 +38,7 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/ito.json b/packages/web3-constants/evm/ito.json
index f15c7c3a47e6..7a56f24e5d98 100644
--- a/packages/web3-constants/evm/ito.json
+++ b/packages/web3-constants/evm/ito.json
@@ -17,7 +17,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"MASK_ITO_CONTRACT_ADDRESS": {
"Mainnet": "0x86812da3A623ab9606976078588b80C315E55FA3",
@@ -37,7 +38,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"ITO2_CONTRACT_ADDRESS": {
"Mainnet": "0xc2CFbF22d6Dc87D0eE18d38d73733524c109Ff46",
@@ -99,7 +101,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"DEFAULT_QUALIFICATION2_ADDRESS": {
"Mainnet": "0x4dC5f343Fe57E4fbDA1B454d125D396A3181272c",
@@ -140,6 +143,7 @@
"Celo": "https://api.thegraph.com/subgraphs/name/dimensiondev/mask-ito-celo",
"Fantom": "https://api.thegraph.com/subgraphs/name/dimensiondev/mask-ito-fantom",
"Aurora": "https://api.thegraph.com/subgraphs/name/dimensiondev/mask-ito-aurora",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/lbp.json b/packages/web3-constants/evm/lbp.json
index 051943e8a62d..4e867a9c5741 100644
--- a/packages/web3-constants/evm/lbp.json
+++ b/packages/web3-constants/evm/lbp.json
@@ -17,6 +17,7 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/mask-box.json b/packages/web3-constants/evm/mask-box.json
index a391a2ea2256..d1bc8e10c0f8 100644
--- a/packages/web3-constants/evm/mask-box.json
+++ b/packages/web3-constants/evm/mask-box.json
@@ -17,7 +17,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "0xf5056B96ab242C566002852d0b98ce0BcDf1af51",
- "Aurora_Testnet": "0xB4D669bc117735FdA44e90e52795132187705B21"
+ "Aurora_Testnet": "0xB4D669bc117735FdA44e90e52795132187705B21",
+ "Conflux": ""
},
"MASK_BOX_CONTRACT_FROM_BLOCK": {
"Mainnet": 13687866,
@@ -37,6 +38,7 @@
"Celo": 0,
"Fantom": 0,
"Aurora": 57259004,
- "Aurora_Testnet": 77919118
+ "Aurora_Testnet": 77919118,
+ "Conflux": 0
}
}
diff --git a/packages/web3-constants/evm/openocean.json b/packages/web3-constants/evm/openocean.json
index 50fb7755a589..a0004c674b66 100644
--- a/packages/web3-constants/evm/openocean.json
+++ b/packages/web3-constants/evm/openocean.json
@@ -17,6 +17,7 @@
"Celo": "0x934B510D4C9103E6a87AEf13b816fb080286D649",
"Fantom": "0x934B510D4C9103E6a87AEf13b816fb080286D649",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/opensea-api.json b/packages/web3-constants/evm/opensea-api.json
index 8032d46e99ce..eec5cbdb1162 100644
--- a/packages/web3-constants/evm/opensea-api.json
+++ b/packages/web3-constants/evm/opensea-api.json
@@ -17,7 +17,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"GET_SINGLE_ASSET_URL": {
"Mainnet": "https://api.opensea.io/api/v1/asset",
@@ -37,7 +38,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"GET_ASSETS_URL": {
"Mainnet": "https://api.opensea.io/api/v1/assets",
@@ -57,6 +59,7 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/pooltogether.json b/packages/web3-constants/evm/pooltogether.json
index 529a2e16bb28..ea3096e69ea4 100644
--- a/packages/web3-constants/evm/pooltogether.json
+++ b/packages/web3-constants/evm/pooltogether.json
@@ -17,7 +17,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"MASK_POOL_ADDRESS": {
"Mainnet": "",
@@ -37,6 +38,7 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/red-packet.json b/packages/web3-constants/evm/red-packet.json
index f88e4a77a16d..3fc873e50bb9 100644
--- a/packages/web3-constants/evm/red-packet.json
+++ b/packages/web3-constants/evm/red-packet.json
@@ -17,7 +17,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"HAPPY_RED_PACKET_ADDRESS_V2": {
"Mainnet": "0x8D8912E1237F9FF3EF661F32743CFB276E052F98",
@@ -37,7 +38,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"HAPPY_RED_PACKET_ADDRESS_V3": {
"Mainnet": "",
@@ -57,7 +59,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"HAPPY_RED_PACKET_ADDRESS_V4": {
"Mainnet": "0xaBBe1101FD8fa5847c452A6D70C8655532B03C33",
@@ -119,6 +122,7 @@
"Celo": "https://api.thegraph.com/subgraphs/name/dimensiondev/mask-red-packet-celo",
"Fantom": "https://api.thegraph.com/subgraphs/name/dimensiondev/mask-red-packet-fantom",
"Aurora": "https://api.thegraph.com/subgraphs/name/dimensiondev/mask-red-packet-aurora",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/savings.json b/packages/web3-constants/evm/savings.json
index 0f58f923eb48..3372aaf2c0d1 100644
--- a/packages/web3-constants/evm/savings.json
+++ b/packages/web3-constants/evm/savings.json
@@ -17,7 +17,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"LIDO_STETH": {
"Mainnet": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84",
@@ -37,7 +38,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"LIDO_REFERRAL_ADDRESS": {
"Mainnet": "0x278D7e418a28ff763eEeDf29238CD6dfcade3A3a",
@@ -57,6 +59,7 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/space-station-galaxy.json b/packages/web3-constants/evm/space-station-galaxy.json
index 86b85572cdcd..28bd2f7c41ed 100644
--- a/packages/web3-constants/evm/space-station-galaxy.json
+++ b/packages/web3-constants/evm/space-station-galaxy.json
@@ -17,7 +17,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"SUBGRAPH_URL": {
"Mainnet": "",
@@ -37,6 +38,7 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-constants/evm/trending.json b/packages/web3-constants/evm/trending.json
index a115fe46afe5..4848e30aa5d4 100644
--- a/packages/web3-constants/evm/trending.json
+++ b/packages/web3-constants/evm/trending.json
@@ -17,7 +17,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"UNISWAP_V2_HEALTH_URL": {
"Mainnet": "https://api.thegraph.com/index-node/graphql",
@@ -37,7 +38,8 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
},
"ETHEREUM_BLOCKS_SUBGRAPH_URL": {
"Mainnet": "https://api.thegraph.com/subgraphs/name/blocklytics/ethereum-blocks",
@@ -57,6 +59,7 @@
"Celo": "",
"Fantom": "",
"Aurora": "",
- "Aurora_Testnet": ""
+ "Aurora_Testnet": "",
+ "Conflux": ""
}
}
diff --git a/packages/web3-providers/src/NextID/constants.ts b/packages/web3-providers/src/NextID/constants.ts
new file mode 100644
index 000000000000..ccaf51f87774
--- /dev/null
+++ b/packages/web3-providers/src/NextID/constants.ts
@@ -0,0 +1,5 @@
+export const MASK_STORAGE_KEY = 'com.mask.plugin'
+export const KV_BASE_URL_DEV = 'https://kv-service.nextnext.id'
+export const KV_BASE_URL_PROD = ''
+export const PROOF_BASE_URL_DEV = 'https://proof-service.nextnext.id/'
+export const PROOF_BASE_URL_PROD = 'https://proof-service.next.id/'
diff --git a/packages/web3-providers/src/NextID/helper.ts b/packages/web3-providers/src/NextID/helper.ts
new file mode 100644
index 000000000000..0708e8ce7e3c
--- /dev/null
+++ b/packages/web3-providers/src/NextID/helper.ts
@@ -0,0 +1,44 @@
+import LRU from 'lru-cache'
+import { Result, Ok, Err } from 'ts-results'
+
+const fetchCache = new LRU({
+ max: 100,
+ ttl: 20000,
+})
+
+export function deleteCache(key: string) {
+ fetchCache.delete(key)
+}
+
+export async function fetchJSON(
+ url: string,
+ requestInit?: RequestInit,
+ enableCache?: boolean,
+): Promise> {
+ type FetchCache = LRU | T>
+
+ const cached = enableCache ? (fetchCache as FetchCache).get(url) : undefined
+ const isPending = cached instanceof Promise
+ if (cached && !isPending) {
+ return Ok(cached)
+ }
+ let pendingResponse: Promise
+ if (isPending) {
+ pendingResponse = cached
+ } else {
+ pendingResponse = globalThis.fetch(url, { mode: 'cors', ...requestInit })
+ if (enableCache) {
+ fetchCache.set(url, pendingResponse)
+ }
+ }
+ const response = await pendingResponse
+
+ const result = await response.clone().json()
+
+ if (result.message || !response.ok) {
+ return Err(result.message)
+ }
+ fetchCache.set(url, result)
+
+ return Ok(result)
+}
diff --git a/packages/web3-providers/src/NextID/index.ts b/packages/web3-providers/src/NextID/index.ts
index 4167ff5f2600..d757a926156f 100644
--- a/packages/web3-providers/src/NextID/index.ts
+++ b/packages/web3-providers/src/NextID/index.ts
@@ -1,178 +1,2 @@
-import {
- BindingProof,
- fromHex,
- NextIDAction,
- NextIDBindings,
- NextIDPayload,
- NextIDPlatform,
- toBase64,
-} from '@masknet/shared-base'
-import LRU from 'lru-cache'
-import urlcat from 'urlcat'
-import { first } from 'lodash-unified'
-
-const BASE_URL =
- process.env.channel === 'stable' && process.env.NODE_ENV === 'production'
- ? 'https://proof-service.next.id/'
- : 'https://proof-service.nextnext.id/'
-
-interface CreatePayloadBody {
- action: string
- platform: string
- identity: string
- public_key: string
-}
-
-type PostContentLanguages = 'default' | 'zh_CN'
-
-interface CreatePayloadResponse {
- post_content: { [key in PostContentLanguages]: string }
- sign_payload: string
- uuid: string
- created_at: string
-}
-
-const fetchCache = new LRU({
- max: 100,
- ttl: 20000,
-})
-
-export async function fetchJSON(
- url: string,
- requestInit?: RequestInit,
- enableCache?: boolean,
-): Promise {
- type FetchCache = LRU | T>
-
- const cached = enableCache ? (fetchCache as FetchCache).get(url) : undefined
- const isPending = cached instanceof Promise
- if (cached && !isPending) {
- return cached
- }
- let pendingResponse: Promise
- if (isPending) {
- pendingResponse = cached
- } else {
- pendingResponse = globalThis.fetch(url, { mode: 'cors', ...requestInit })
- if (enableCache) {
- fetchCache.set(url, pendingResponse)
- }
- }
- const response = await pendingResponse
-
- const result = await response.clone().json()
-
- if (result.message || !response.ok) {
- throw new Error(result.message)
- }
- fetchCache.set(url, result)
- return result
-}
-
-// TODO: remove 'bind' in project for business context.
-export async function bindProof(
- uuid: string,
- personaPublicKey: string,
- action: NextIDAction,
- platform: string,
- identity: string,
- createdAt: string,
- options?: {
- walletSignature?: string
- signature?: string
- proofLocation?: string
- },
-) {
- const requestBody = {
- uuid,
- action,
- platform,
- identity,
- public_key: personaPublicKey,
- proof_location: options?.proofLocation,
- extra: {
- wallet_signature: options?.walletSignature ? toBase64(fromHex(options.walletSignature)) : undefined,
- signature: options?.signature ? toBase64(fromHex(options.signature)) : undefined,
- },
- created_at: createdAt,
- }
-
- return fetchJSON(urlcat(BASE_URL, '/v1/proof'), {
- body: JSON.stringify(requestBody),
- method: 'POST',
- })
-}
-
-export async function queryExistedBindingByPersona(personaPublicKey: string, enableCache?: boolean) {
- const response = await fetchJSON(
- urlcat(BASE_URL, '/v1/proof', { platform: NextIDPlatform.NextId, identity: personaPublicKey }),
- {},
- enableCache,
- )
- // Will have only one item when query by personaPublicKey
- return first(response.ids)
-}
-
-export async function queryExistedBindingByPlatform(platform: NextIDPlatform, identity: string, page?: number) {
- if (!platform && !identity) return []
-
- const response = await fetchJSON(
- urlcat(BASE_URL, '/v1/proof', { platform: platform, identity: identity }),
- )
-
- // TODO: merge Pagination into this
- return response.ids
-}
-
-export async function queryIsBound(
- personaPublicKey: string,
- platform: NextIDPlatform,
- identity: string,
- enableCache?: boolean,
-) {
- if (!platform && !identity) return false
-
- try {
- await fetchJSON(
- urlcat(BASE_URL, '/v1/proof/exists', {
- platform: platform,
- identity: identity,
- public_key: personaPublicKey,
- }),
- {},
- enableCache,
- )
- return true
- } catch {
- return false
- }
-}
-
-export async function createPersonaPayload(
- personaPublicKey: string,
- action: NextIDAction,
- identity: string,
- platform: NextIDPlatform,
- language?: string,
-): Promise {
- const requestBody: CreatePayloadBody = {
- action,
- platform,
- identity,
- public_key: personaPublicKey,
- }
-
- const nextIDLanguageFormat = language?.replace('-', '_') as PostContentLanguages
-
- const response = await fetchJSON(urlcat(BASE_URL, '/v1/proof/payload'), {
- body: JSON.stringify(requestBody),
- method: 'POST',
- })
-
- return {
- postContent: response.post_content[nextIDLanguageFormat ?? 'default'] ?? response.post_content.default,
- signPayload: JSON.stringify(JSON.parse(response.sign_payload)),
- createdAt: response.created_at,
- uuid: response.uuid,
- }
-}
+export * from './kv'
+export * from './proof'
diff --git a/packages/web3-providers/src/NextID/kv.ts b/packages/web3-providers/src/NextID/kv.ts
new file mode 100644
index 000000000000..3598383851d8
--- /dev/null
+++ b/packages/web3-providers/src/NextID/kv.ts
@@ -0,0 +1,111 @@
+/**
+ * Document url: https://github.com/nextdotid/kv_server/blob/develop/docs/api.apib
+ */
+import urlcat from 'urlcat'
+import type { NextIDStoragePayload, NextIDPlatform } from '@masknet/shared-base'
+import { fetchJSON } from './helper'
+import type { Result } from 'ts-results'
+import type { NextIDBaseAPI } from '../types'
+import { KV_BASE_URL_DEV, KV_BASE_URL_PROD, MASK_STORAGE_KEY } from './constants'
+
+interface CreatePayloadResponse {
+ uuid: string
+ sign_payload: string
+ created_at: string
+}
+
+const BASE_URL =
+ process.env.channel === 'stable' && process.env.NODE_ENV === 'production' ? KV_BASE_URL_PROD : KV_BASE_URL_DEV
+
+function formatPatchData(platform: NextIDPlatform, identity: string, data: unknown) {
+ return {
+ [MASK_STORAGE_KEY]: {
+ [`${platform}_${identity}`]: data,
+ },
+ }
+}
+
+export class NextIDStorageAPI implements NextIDBaseAPI.Storage {
+ /**
+ * Get current KV of a persona
+ * @param personaPublicKey
+ *
+ */
+ async get(personaPublicKey: string): Promise> {
+ const full = await fetchJSON<{ [MASK_STORAGE_KEY]: T }>(
+ urlcat(BASE_URL, '/v1/kv', { persona: personaPublicKey }),
+ )
+ return full.map((x) => x[MASK_STORAGE_KEY])
+ }
+
+ /**
+ * Get signature payload for updating
+ * @param personaPublicKey
+ * @param platform
+ * @param identity
+ * @param patchData
+ *
+ * We choose [RFC 7396](https://www.rfc-editor.org/rfc/rfc7396) standard for KV modifying.
+ */
+ async getPayload(
+ personaPublicKey: string,
+ platform: NextIDPlatform,
+ identity: string,
+ patchData: unknown,
+ ): Promise> {
+ const requestBody = {
+ persona: personaPublicKey,
+ platform,
+ identity,
+ patch: formatPatchData(platform, identity, patchData),
+ }
+
+ const response = await fetchJSON(urlcat(BASE_URL, '/v1/kv/payload'), {
+ body: JSON.stringify(requestBody),
+ method: 'POST',
+ })
+
+ return response.map((x) => ({
+ signPayload: JSON.stringify(JSON.parse(x.sign_payload)),
+ createdAt: x.created_at,
+ uuid: x.uuid,
+ }))
+ }
+
+ /**
+ * Update a full set of key-value pairs
+ * @param uuid
+ * @param personaPublicKey
+ * @param signature
+ * @param platform
+ * @param identity
+ * @param createdAt
+ * @param patchData
+ *
+ * We choose [RFC 7396](https://www.rfc-editor.org/rfc/rfc7396) standard for KV modifying.
+ */
+ set(
+ uuid: string,
+ personaPublicKey: string,
+ signature: string,
+ platform: NextIDPlatform,
+ identity: string,
+ createdAt: string,
+ patchData: unknown,
+ ): Promise> {
+ const requestBody = {
+ uuid,
+ persona: personaPublicKey,
+ platform,
+ identity,
+ signature,
+ patch: formatPatchData(platform, identity, patchData),
+ created_at: createdAt,
+ }
+
+ return fetchJSON(urlcat(BASE_URL, '/v1/kv'), {
+ body: JSON.stringify(requestBody),
+ method: 'POST',
+ })
+ }
+}
diff --git a/packages/web3-providers/src/NextID/proof.ts b/packages/web3-providers/src/NextID/proof.ts
new file mode 100644
index 000000000000..6a134c0e4d42
--- /dev/null
+++ b/packages/web3-providers/src/NextID/proof.ts
@@ -0,0 +1,148 @@
+import { deleteCache, fetchJSON } from './helper'
+import urlcat from 'urlcat'
+import { first } from 'lodash-unified'
+import {
+ BindingProof,
+ fromHex,
+ NextIDAction,
+ NextIDBindings,
+ NextIDPayload,
+ NextIDPlatform,
+ toBase64,
+} from '@masknet/shared-base'
+import type { NextIDBaseAPI } from '../types'
+import { PROOF_BASE_URL_DEV, PROOF_BASE_URL_PROD } from './constants'
+
+const BASE_URL =
+ process.env.channel === 'stable' && process.env.NODE_ENV === 'production' ? PROOF_BASE_URL_PROD : PROOF_BASE_URL_DEV
+
+interface CreatePayloadBody {
+ action: string
+ platform: string
+ identity: string
+ public_key: string
+}
+
+type PostContentLanguages = 'default' | 'zh_CN'
+
+interface CreatePayloadResponse {
+ post_content: { [key in PostContentLanguages]: string }
+ sign_payload: string
+ uuid: string
+ created_at: string
+}
+
+const getPersonaQueryURL = (platform: string, identity: string) =>
+ urlcat(BASE_URL, '/v1/proof', {
+ platform,
+ identity,
+ })
+
+const geyExistedBindingQueryURL = (platform: string, identity: string, personaPublicKey: string) =>
+ urlcat(BASE_URL, '/v1/proof/exists', {
+ platform,
+ identity,
+ public_key: personaPublicKey,
+ })
+
+export class NextIDProofAPI implements NextIDBaseAPI.Proof {
+ // TODO: remove 'bind' in project for business context.
+ async bindProof(
+ uuid: string,
+ personaPublicKey: string,
+ action: NextIDAction,
+ platform: string,
+ identity: string,
+ createdAt: string,
+ options?: {
+ walletSignature?: string
+ signature?: string
+ proofLocation?: string
+ },
+ ) {
+ const requestBody = {
+ uuid,
+ action,
+ platform,
+ identity,
+ public_key: personaPublicKey,
+ proof_location: options?.proofLocation,
+ extra: {
+ wallet_signature: options?.walletSignature ? toBase64(fromHex(options.walletSignature)) : undefined,
+ signature: options?.signature ? toBase64(fromHex(options.signature)) : undefined,
+ },
+ created_at: createdAt,
+ }
+
+ const result = await fetchJSON(urlcat(BASE_URL, '/v1/proof'), {
+ body: JSON.stringify(requestBody),
+ method: 'POST',
+ })
+
+ // Should delete cache when proof status changed
+ const cacheKeyOfQueryPersona = getPersonaQueryURL(NextIDPlatform.NextId, personaPublicKey)
+ const cacheKeyOfExistedBinding = geyExistedBindingQueryURL(platform, identity, personaPublicKey)
+ deleteCache(cacheKeyOfQueryPersona)
+ deleteCache(cacheKeyOfExistedBinding)
+
+ return result
+ }
+
+ async queryExistedBindingByPersona(personaPublicKey: string, enableCache?: boolean) {
+ const url = getPersonaQueryURL(NextIDPlatform.NextId, personaPublicKey)
+ const response = await fetchJSON(url, {}, enableCache)
+ // Will have only one item when query by personaPublicKey
+ return first(response.unwrap().ids)
+ }
+
+ async queryExistedBindingByPlatform(platform: NextIDPlatform, identity: string, page?: number) {
+ if (!platform && !identity) return []
+
+ const response = await fetchJSON(
+ urlcat(BASE_URL, '/v1/proof', { platform: platform, identity: identity }),
+ )
+
+ // TODO: merge Pagination into this
+ return response.unwrap().ids
+ }
+
+ async queryIsBound(personaPublicKey: string, platform: NextIDPlatform, identity: string, enableCache?: boolean) {
+ if (!platform && !identity) return false
+
+ const url = geyExistedBindingQueryURL(platform, identity, personaPublicKey)
+ const result = await fetchJSON(url, {}, enableCache)
+
+ return result.map(() => true).unwrapOr(false)
+ }
+
+ async createPersonaPayload(
+ personaPublicKey: string,
+ action: NextIDAction,
+ identity: string,
+ platform: NextIDPlatform,
+ language?: string,
+ ): Promise {
+ const requestBody: CreatePayloadBody = {
+ action,
+ platform,
+ identity,
+ public_key: personaPublicKey,
+ }
+
+ const nextIDLanguageFormat = language?.replace('-', '_') as PostContentLanguages
+
+ const response = await fetchJSON(urlcat(BASE_URL, '/v1/proof/payload'), {
+ body: JSON.stringify(requestBody),
+ method: 'POST',
+ })
+
+ return response
+ .map((x) => ({
+ postContent: x.post_content[nextIDLanguageFormat ?? 'default'] ?? x.post_content.default,
+ signPayload: JSON.stringify(JSON.parse(x.sign_payload)),
+ createdAt: x.created_at,
+ uuid: x.uuid,
+ }))
+ .unwrapOr(null)
+ }
+}
diff --git a/packages/web3-providers/src/gopluslabs/constants.ts b/packages/web3-providers/src/gopluslabs/constants.ts
index 79eb1f0198ed..3d052ffb1f90 100644
--- a/packages/web3-providers/src/gopluslabs/constants.ts
+++ b/packages/web3-providers/src/gopluslabs/constants.ts
@@ -1 +1 @@
-export const GO_PLUS_LABS_ROOT_URL = 'https://api.gopluslabs.io'
+export const GO_PLUS_LABS_ROOT_URL = 'https://gopluslabs.r2d2.to'
diff --git a/packages/web3-providers/src/gopluslabs/index.ts b/packages/web3-providers/src/gopluslabs/index.ts
index a8f1c605b9d4..ba681d537f16 100644
--- a/packages/web3-providers/src/gopluslabs/index.ts
+++ b/packages/web3-providers/src/gopluslabs/index.ts
@@ -1,15 +1,24 @@
-import { uniqBy } from 'lodash-unified'
+import { ChainId } from '@masknet/web3-shared-evm'
+import { parseInt, uniqBy } from 'lodash-unified'
import urlcat from 'urlcat'
import type { SecurityAPI } from '..'
import { fetchJSON } from '../helpers'
import { GO_PLUS_LABS_ROOT_URL } from './constants'
+export interface SupportedChainResponse {
+ id: string
+ name: string
+}
+
export class GoPlusLabsAPI implements SecurityAPI.Provider {
- async getTokenSecurity(chainId: number, listOfAddress: string[]) {
+ async getTokenSecurity(chainId: ChainId, listOfAddress: string[]) {
const response = await fetchJSON<{
code: 0 | 1
message: 'OK' | string
- result: Record
+ result: Record<
+ string,
+ SecurityAPI.ContractSecurity & SecurityAPI.TokenSecurity & SecurityAPI.TradingSecurity
+ >
}>(
urlcat(GO_PLUS_LABS_ROOT_URL, 'api/v1/token_security/:id', {
id: chainId,
@@ -20,4 +29,15 @@ export class GoPlusLabsAPI implements SecurityAPI.Provider {
if (response.code !== 1) return
return response.result
}
+
+ async getSupportedChain(): Promise {
+ const { code, result } = await fetchJSON<{
+ code: 0 | 1
+ message: 'OK' | string
+ result: SupportedChainResponse[]
+ }>(urlcat(GO_PLUS_LABS_ROOT_URL, 'api/v1/supported_chains'))
+
+ if (code !== 1) return []
+ return result.map((x) => ({ id: parseInt(x.id) ?? ChainId.Mainnet, name: x.name }))
+ }
}
diff --git a/packages/web3-providers/src/index.ts b/packages/web3-providers/src/index.ts
index d3989c5950fc..319cb457e9eb 100644
--- a/packages/web3-providers/src/index.ts
+++ b/packages/web3-providers/src/index.ts
@@ -10,6 +10,8 @@ import { TwitterAPI } from './twitter'
import { TokenListAPI } from './token-list'
import { TokenPriceAPI } from './token-price'
import { InstagramAPI } from './instagram'
+import { GoPlusLabsAPI } from './gopluslabs'
+import { NextIDProofAPI, NextIDStorageAPI } from './NextID'
export * from './types'
export * from './hooks'
@@ -26,8 +28,12 @@ export const RSS3 = new RSS3API()
export const KeyValue = new KeyValueAPI()
export const Twitter = new TwitterAPI()
export const Instagram = new InstagramAPI()
+export const GoPlusLabs = new GoPlusLabsAPI()
+
export const TokenList = new TokenListAPI()
export const TokenPrice = new TokenPriceAPI()
+export const NextIDStorage = new NextIDStorageAPI()
+export const NextIDProof = new NextIDProofAPI()
// Method for provider proxy
export { getOpenSeaNFTList, getOpenSeaCollectionList } from './opensea'
diff --git a/packages/web3-providers/src/types.ts b/packages/web3-providers/src/types.ts
index 5e4a0def0320..4a3972fb087b 100644
--- a/packages/web3-providers/src/types.ts
+++ b/packages/web3-providers/src/types.ts
@@ -8,6 +8,8 @@ import type {
NativeTokenDetailed,
} from '@masknet/web3-shared-evm'
import type { CurrencyType } from '@masknet/plugin-infra'
+import type { Result } from 'ts-results'
+import type { NextIDAction, NextIDStoragePayload, NextIDPayload, NextIDPlatform } from '@masknet/shared-base'
export namespace ExplorerAPI {
export type Transaction = Web3Transaction & {
@@ -308,25 +310,97 @@ export namespace StorageAPI {
}
}
+export namespace NextIDBaseAPI {
+ export interface Storage {
+ set(
+ uuid: string,
+ personaPublicKey: string,
+ signature: string,
+ platform: NextIDPlatform,
+ identity: string,
+ createdAt: string,
+ patchData: unknown,
+ ): Promise>
+ get(key: string): Promise>
+ getPayload(
+ personaPublicKey: string,
+ platform: NextIDPlatform,
+ identity: string,
+ patchData: unknown,
+ ): Promise>
+ }
+ export interface Proof {
+ bindProof(
+ uuid: string,
+ personaPublicKey: string,
+ action: NextIDAction,
+ platform: string,
+ identity: string,
+ createdAt: string,
+ options?: {
+ walletSignature?: string
+ signature?: string
+ proofLocation?: string
+ },
+ ): Promise>
+
+ queryExistedBindingByPersona(personaPublicKey: string, enableCache?: boolean): Promise
+
+ queryExistedBindingByPlatform(platform: NextIDPlatform, identity: string, page?: number): Promise
+
+ queryIsBound(
+ personaPublicKey: string,
+ platform: NextIDPlatform,
+ identity: string,
+ enableCache?: boolean,
+ ): Promise
+
+ createPersonaPayload(
+ personaPublicKey: string,
+ action: NextIDAction,
+ identity: string,
+ platform: NextIDPlatform,
+ language?: string,
+ ): Promise
+ }
+}
+
export namespace SecurityAPI {
export interface Holder {
address?: string
- locked?: 0 | 1
+ locked?: '0' | '1'
tag?: string
- is_contract?: 0 | 1
+ is_contract?: '0' | '1'
balance?: number
percent?: number
}
+ export interface TradingSecurity {
+ buy_tax?: string
+ sell_tax?: string
+ slippage_modifiable?: '0' | '1'
+ is_honeypot?: '0' | '1'
+ transfer_pausable?: '0' | '1'
+ is_blacklisted?: '0' | '1'
+ is_whitelisted?: '0' | '1'
+ is_in_dex?: '0' | '1'
+ is_anti_whale?: '0' | '1'
+ }
+
export interface ContractSecurity {
- is_open_source?: 0 | 1
- is_proxy?: 0 | 1
- is_mintable?: 0 | 1
- can_take_back_ownership?: string
+ is_open_source?: '0' | '1'
+ is_proxy?: '0' | '1'
+ is_mintable?: '0' | '1'
+ owner_change_balance?: '0' | '1'
+ can_take_back_ownership?: '0' | '1'
owner_address?: string
+ creator_address?: string
}
export interface TokenSecurity {
+ token_name?: string
+ token_symbol?: string
+
holder_count?: number
total_supply?: number
holders?: Holder[]
@@ -335,16 +409,22 @@ export namespace SecurityAPI {
lp_total_supply?: number
lp_holders?: Holder[]
- is_true_token?: 0 | 1
- is_verifiable_team?: 0 | 1
- is_airdrop_scam?: 0 | 1
+ is_true_token?: '0' | '1'
+ is_verifiable_team?: '0' | '1'
+ is_airdrop_scam?: '0' | '1'
+ }
+
+ export interface SupportedChain {
+ id: ChainId
+ name: string
}
export interface Provider {
getTokenSecurity(
chainId: number,
listOfAddress: string[],
- ): Promise | void>
+ ): Promise | void>
+ getSupportedChain(): Promise
}
}
diff --git a/packages/web3-shared/base/src/utils/number.ts b/packages/web3-shared/base/src/utils/number.ts
index 042a5b0b6deb..b061820adf29 100644
--- a/packages/web3-shared/base/src/utils/number.ts
+++ b/packages/web3-shared/base/src/utils/number.ts
@@ -44,6 +44,7 @@ export function multipliedBy(a: BigNumber.Value, b: BigNumber.Value) {
}
/** 10 ** n */
+/** @deprecated use scale10 */
export function pow10(n: BigNumber.Value) {
return new BigNumber(10).pow(n)
}
diff --git a/packages/web3-shared/evm/hooks/useAssets.ts b/packages/web3-shared/evm/hooks/useAssets.ts
index 9ca15511ba4b..d90a63d611a2 100644
--- a/packages/web3-shared/evm/hooks/useAssets.ts
+++ b/packages/web3-shared/evm/hooks/useAssets.ts
@@ -1,3 +1,4 @@
+import { EMPTY_LIST } from '@masknet/shared-base'
import type { ChainId, FungibleTokenDetailed } from '../types'
import { useWallet } from './useWallet'
import { useNativeTokenDetailed } from './useNativeTokenDetailed'
@@ -5,7 +6,7 @@ import { useAssetsFromChain } from './useAssetsFromChain'
import { useAssetsFromProvider } from './useAssetsFromProvider'
import { useCallback, useMemo } from 'react'
import { useAssetsMerged } from './useAssetsMerged'
-import { formatEthereumAddress, EMPTY_LIST } from '../utils'
+import { formatEthereumAddress } from '../utils'
export function useAssets(tokens: FungibleTokenDetailed[], chainId?: ChainId | 'all') {
const wallet = useWallet()
diff --git a/packages/web3-shared/evm/hooks/useAssetsFromChain.ts b/packages/web3-shared/evm/hooks/useAssetsFromChain.ts
index 28d0a326453b..ac4cb59a1cde 100644
--- a/packages/web3-shared/evm/hooks/useAssetsFromChain.ts
+++ b/packages/web3-shared/evm/hooks/useAssetsFromChain.ts
@@ -1,9 +1,10 @@
import { useMemo } from 'react'
import { first } from 'lodash-unified'
+import { EMPTY_LIST } from '@masknet/shared-base'
import { Asset, ChainId, EthereumTokenType, FungibleTokenDetailed } from '../types'
import { useTokensBalance } from './useTokensBalance'
import { useChainDetailed } from './useChainDetailed'
-import { getChainDetailed, EMPTY_LIST } from '../utils'
+import { getChainDetailed } from '../utils'
import { useBalance } from './useBalance'
export function useAssetsFromChain(tokens: FungibleTokenDetailed[], chainId?: ChainId) {
diff --git a/packages/web3-shared/evm/hooks/useAssetsMerged.ts b/packages/web3-shared/evm/hooks/useAssetsMerged.ts
index 231f8d9b6a89..36b25f1fece4 100644
--- a/packages/web3-shared/evm/hooks/useAssetsMerged.ts
+++ b/packages/web3-shared/evm/hooks/useAssetsMerged.ts
@@ -1,8 +1,9 @@
-import { uniqBy } from 'lodash-unified'
import { useMemo } from 'react'
+import { uniqBy } from 'lodash-unified'
+import { EMPTY_LIST } from '@masknet/shared-base'
import { useTokenConstants } from '../constants'
import type { Asset } from '../types'
-import { formatEthereumAddress, makeSortAssertFn, EMPTY_LIST } from '../utils'
+import { formatEthereumAddress, makeSortAssertFn } from '../utils'
import { useChainId } from './useChainId'
/**
diff --git a/packages/web3-shared/evm/hooks/useERC721ContractDetailed.ts b/packages/web3-shared/evm/hooks/useERC721ContractDetailed.ts
index 4ca404e8db20..c99f91b9e31b 100644
--- a/packages/web3-shared/evm/hooks/useERC721ContractDetailed.ts
+++ b/packages/web3-shared/evm/hooks/useERC721ContractDetailed.ts
@@ -5,39 +5,14 @@ import type { ChainId } from '../types'
import { useChainId } from './useChainId'
import { useERC721TokenContract } from '../contracts/useERC721TokenContract'
import { createERC721ContractDetailed, safeNonPayableTransactionCall } from '../utils'
-import { useOpenseaAPIConstants } from '../constants'
+import { getOpenseaAPIConstants } from '../constants'
export function useERC721ContractDetailed(address?: string) {
const chainId = useChainId()
- const { GET_CONTRACT_URL } = useOpenseaAPIConstants()
const erc721TokenContract = useERC721TokenContract(address)
return useAsyncRetry(async () => {
if (!address || !EthereumAddress.isValid(address) || !erc721TokenContract) return
-
- const erc721ContractDetailedFromChain = await getERC721ContractDetailedFromChain(
- address,
- chainId,
- erc721TokenContract,
- )
-
- if (!GET_CONTRACT_URL) return erc721ContractDetailedFromChain
-
- const contractDetailedFromOpensea = await getERC721ContractDetailedFromOpensea(
- address,
- chainId,
- GET_CONTRACT_URL,
- )
-
- // We prefer to use `name` and `symbol` from chain rather than opensea since,
- // these two data on opensea is sometimes incorrect. Meanwhile there's often
- // a lack of `iconURL` from chain, which exists on opensea.
- return contractDetailedFromOpensea
- ? {
- ...contractDetailedFromOpensea,
- name: erc721ContractDetailedFromChain.name,
- symbol: erc721ContractDetailedFromChain.symbol,
- }
- : erc721ContractDetailedFromChain
+ return getERC721ContractDetailed(erc721TokenContract, address, chainId)
}, [address, chainId, erc721TokenContract])
}
@@ -69,3 +44,21 @@ async function getERC721ContractDetailedFromOpensea(address: string, chainId: Ch
}
return null
}
+
+export async function getERC721ContractDetailed(contract: ERC721, address: string, chainId: ChainId) {
+ const erc721ContractDetailedFromChain = await getERC721ContractDetailedFromChain(address, chainId, contract)
+ const { GET_CONTRACT_URL } = getOpenseaAPIConstants(chainId)
+ if (!GET_CONTRACT_URL) return erc721ContractDetailedFromChain
+ const contractDetailedFromOpensea = await getERC721ContractDetailedFromOpensea(address, chainId, GET_CONTRACT_URL)
+
+ // We prefer to use `name` and `symbol` from chain rather than opensea since,
+ // these two data on opensea is sometimes incorrect. Meanwhile there's often
+ // a lack of `iconURL` from chain, which exists on opensea.
+ return contractDetailedFromOpensea
+ ? {
+ ...contractDetailedFromOpensea,
+ name: erc721ContractDetailedFromChain.name,
+ symbol: erc721ContractDetailedFromChain.symbol,
+ }
+ : erc721ContractDetailedFromChain
+}
diff --git a/packages/web3-shared/evm/hooks/useERC721TokenDetailed.ts b/packages/web3-shared/evm/hooks/useERC721TokenDetailed.ts
index e120f5d5616a..6dd2eb3d033f 100644
--- a/packages/web3-shared/evm/hooks/useERC721TokenDetailed.ts
+++ b/packages/web3-shared/evm/hooks/useERC721TokenDetailed.ts
@@ -1,37 +1,25 @@
+import type { ERC721 } from '@masknet/web3-contracts/types/ERC721'
+import { first, noop } from 'lodash-unified'
import { useAsyncRetry } from 'react-use'
-import { useRef } from 'react'
import urlcat from 'urlcat'
-import { first, noop } from 'lodash-unified'
-import type { ERC721ContractDetailed, ERC721TokenInfo, ERC721TokenDetailed } from '../types'
+import { getOpenseaAPIConstants } from '../constants'
import { useERC721TokenContract } from '../contracts/useERC721TokenContract'
-import { safeNonPayableTransactionCall, createERC721Token, formatNFT_TokenId } from '../utils'
-import type { ERC721 } from '@masknet/web3-contracts/types/ERC721'
-import { useOpenseaAPIConstants } from '../constants'
+import type { ChainId, ERC721ContractDetailed, ERC721TokenInfo } from '../types'
+import { createERC721Token, formatNFT_TokenId, safeNonPayableTransactionCall } from '../utils'
+import { useChainId } from './useChainId'
export function useERC721TokenDetailed(
contractDetailed: ERC721ContractDetailed | null | undefined,
tokenId: string | null | undefined,
) {
- const { GET_SINGLE_ASSET_URL } = useOpenseaAPIConstants()
const erc721TokenContract = useERC721TokenContract(contractDetailed?.address ?? '')
- const tokenDetailedRef = useRef()
+ const chainId = useChainId()
const asyncRetry = useAsyncRetry(async () => {
if (!erc721TokenContract || !contractDetailed || !tokenId || !contractDetailed.address) return
- tokenDetailedRef.current = GET_SINGLE_ASSET_URL
- ? await getERC721TokenDetailedFromOpensea(contractDetailed, tokenId, GET_SINGLE_ASSET_URL)
- : await getERC721TokenDetailedFromChain(contractDetailed, erc721TokenContract, tokenId)
- const info = await getERC721TokenAssetFromChain(tokenDetailedRef.current?.info.tokenURI)
-
- if (info && tokenDetailedRef.current)
- tokenDetailedRef.current.info = {
- ...info,
- ...tokenDetailedRef.current.info,
- hasTokenDetailed: true,
- name: info.name ?? tokenDetailedRef.current.info.name,
- }
- }, [tokenId, JSON.stringify(contractDetailed), erc721TokenContract, GET_SINGLE_ASSET_URL])
+ return getERC721TokenDetailed(contractDetailed, erc721TokenContract, tokenId, chainId)
+ }, [tokenId, JSON.stringify(contractDetailed), erc721TokenContract])
- return { asyncRetry, tokenDetailed: tokenDetailedRef.current }
+ return { asyncRetry, tokenDetailed: asyncRetry.value }
}
export async function getERC721TokenDetailedFromOpensea(
@@ -142,3 +130,25 @@ export async function getERC721TokenAssetFromChain(tokenURI?: string): Promise {
+ setTransferState({
+ type: TransactionStateType.UNKNOWN,
+ })
if (!account || !recipient || !tokenId || !erc721Contract) {
- setTransferState({
- type: TransactionStateType.UNKNOWN,
- })
return
}
diff --git a/packages/web3-shared/evm/hooks/useSocket.ts b/packages/web3-shared/evm/hooks/useSocket.ts
index 232b6f8ce36b..60ed5774c8cc 100644
--- a/packages/web3-shared/evm/hooks/useSocket.ts
+++ b/packages/web3-shared/evm/hooks/useSocket.ts
@@ -18,7 +18,7 @@ export const useSocket = (message: SocketMessage) => {
const [data, setData] = useState([])
const [state, setState] = useState(SocketState.init)
const [error, setError] = useState()
- const [id, setId] = useState(uuid())
+ const [id, setId] = useState(uuid)
const { value: socket, loading } = useAsyncRetry(() => providerSocket, [])
const requestId = `${message.id}_${id}`
diff --git a/packages/web3-shared/evm/hooks/useTokensBalance.ts b/packages/web3-shared/evm/hooks/useTokensBalance.ts
index f5c0f5d2a272..263b47242aac 100644
--- a/packages/web3-shared/evm/hooks/useTokensBalance.ts
+++ b/packages/web3-shared/evm/hooks/useTokensBalance.ts
@@ -1,10 +1,10 @@
import { useAsyncRetry } from 'react-use'
+import { numberToHex } from 'web3-utils'
+import { EMPTY_LIST } from '@masknet/shared-base'
import { useBalanceCheckerContract } from '../contracts/useBalanceChecker'
import { useAccount } from './useAccount'
import { useChainId } from './useChainId'
import type { ChainId } from '../types'
-import { EMPTY_LIST } from '../utils'
-import { numberToHex } from 'web3-utils'
/**
* Fetch balance of multiple tokens from chain
diff --git a/packages/web3-shared/evm/tsconfig.json b/packages/web3-shared/evm/tsconfig.json
index 31e14ea11894..209fa3f77a5f 100644
--- a/packages/web3-shared/evm/tsconfig.json
+++ b/packages/web3-shared/evm/tsconfig.json
@@ -6,5 +6,5 @@
"tsBuildInfoFile": "../dist/evm.tsbuildinfo"
},
"include": ["./", "./**/*.json"],
- "references": [{ "path": "../../injected-script/sdk" }, { "path": "../base" }]
+ "references": [{ "path": "../base" }, { "path": "../../shared-base" }, { "path": "../../injected-script/sdk" }]
}
diff --git a/packages/web3-shared/evm/types/index.ts b/packages/web3-shared/evm/types/index.ts
index 18588cb9e6d5..279218585455 100644
--- a/packages/web3-shared/evm/types/index.ts
+++ b/packages/web3-shared/evm/types/index.ts
@@ -15,14 +15,6 @@ export enum CurrencyType {
USD = 'usd',
}
-export interface PriceRecord {
- [currency: string]: number
-}
-
-/** Base on response of coingecko's token price API */
-export interface CryptoPrice {
- [token: string]: PriceRecord
-}
export type ChainIdOptionalRecord = { [k in ChainId]?: T }
export type ChainIdRecord = { [k in ChainId]: T }
@@ -83,42 +75,12 @@ export enum ChainId {
Conflux = 1030,
}
-export enum ProviderType {
- MaskWallet = 'Maskbook',
- MetaMask = 'MetaMask',
- WalletConnect = 'WalletConnect',
- Fortmatic = 'Fortmatic',
- Coin98 = 'Coin98',
- MathWallet = 'MathWallet',
- WalletLink = 'WalletLink',
- CustomNetwork = 'CustomNetwork',
-}
-
export enum LockStatus {
INIT = 0,
UNLOCK = 1,
LOCKED = 2,
}
-// If you change this enum, please sync it to packages/public-api/src/web.ts
-// (it's a breaking change. Please notify the iOS and Android dev)
-export enum NetworkType {
- Ethereum = 'Ethereum',
- Binance = 'Binance',
- Polygon = 'Polygon',
- Arbitrum = 'Arbitrum',
- xDai = 'xDai',
- Celo = 'Celo',
- Fantom = 'Fantom',
- Aurora = 'Aurora',
- Avalanche = 'Avalanche',
- Boba = 'Boba',
- Fuse = 'Fuse',
- Metis = 'Metis',
- Optimistic = 'Optimistic',
- Conflux = 'Conflux',
-}
-
export interface Wallet {
/** User define wallet name. Default address.prefix(6) */
name: string
@@ -579,18 +541,6 @@ export enum DomainProvider {
UNS = 'UNS',
}
-export enum FungibleAssetProvider {
- ZERION = 'Zerion',
- DEBANK = 'Debank',
-}
-
-export enum NonFungibleAssetProvider {
- OPENSEA = 'OpenSea',
- RARIBLE = 'Rarible',
- NFTSCAN = 'NFTScan',
- ZORA = 'Zora',
-}
-
export type UnboxTransactionObject = T extends NonPayableTransactionObject
? R
: T extends PayableTransactionObject
@@ -691,3 +641,55 @@ export enum TransactionStateType {
/** Fail to send */
FAILED = 5,
}
+
+/**
+ * Keep updating to packages/public-api/src/web.ts
+ */
+export enum NetworkType {
+ Ethereum = 'Ethereum',
+ Binance = 'Binance',
+ Polygon = 'Polygon',
+ Arbitrum = 'Arbitrum',
+ xDai = 'xDai',
+ Celo = 'Celo',
+ Fantom = 'Fantom',
+ Aurora = 'Aurora',
+ Avalanche = 'Avalanche',
+ Boba = 'Boba',
+ Fuse = 'Fuse',
+ Metis = 'Metis',
+ Optimistic = 'Optimistic',
+ Conflux = 'Conflux',
+}
+
+export enum ProviderType {
+ MaskWallet = 'Maskbook',
+ MetaMask = 'MetaMask',
+ WalletConnect = 'WalletConnect',
+ Fortmatic = 'Fortmatic',
+ Coin98 = 'Coin98',
+ MathWallet = 'MathWallet',
+ WalletLink = 'WalletLink',
+ CustomNetwork = 'CustomNetwork',
+}
+
+export enum FungibleAssetProvider {
+ ZERION = 'Zerion',
+ DEBANK = 'Debank',
+}
+
+export enum NonFungibleAssetProvider {
+ OPENSEA = 'OpenSea',
+ RARIBLE = 'Rarible',
+ NFTSCAN = 'NFTScan',
+ ZORA = 'Zora',
+}
+
+export interface PriceRecord {
+ [currency: string]: number
+}
+
+/** Base on response of coingecko's token price API */
+export interface CryptoPrice {
+ [token: string]: PriceRecord
+}
diff --git a/packages/web3-shared/evm/utils/address.ts b/packages/web3-shared/evm/utils/address.ts
index 6cec9e201dfe..2bb6247e301e 100644
--- a/packages/web3-shared/evm/utils/address.ts
+++ b/packages/web3-shared/evm/utils/address.ts
@@ -1,6 +1,6 @@
-import { getEnumAsArray } from '@dimensiondev/kit'
-import { compact, castArray, uniq } from 'lodash-unified'
import { EthereumAddress } from 'wallet.ts'
+import { compact, castArray, uniq } from 'lodash-unified'
+import { getEnumAsArray } from '@dimensiondev/kit'
import { getRedPacketConstants, getTokenConstants, ZERO_ADDRESS } from '../constants'
import { ChainId } from '../types'
diff --git a/packages/web3-shared/evm/utils/formatter.ts b/packages/web3-shared/evm/utils/formatter.ts
index 8a80d99b4564..1c907af9d583 100644
--- a/packages/web3-shared/evm/utils/formatter.ts
+++ b/packages/web3-shared/evm/utils/formatter.ts
@@ -59,7 +59,7 @@ export function formatEthereumAddress(address: string, size = 0) {
}
export function formatNFT_TokenId(tokenId: string, size = 0) {
- if (tokenId.length < 9) return tokenId
+ if (tokenId.length < 9) return `#${tokenId}`
return `#${tokenId.substr(0, 2 + size)}...${tokenId.substr(-size)}`
}
diff --git a/packages/web3-shared/evm/utils/index.ts b/packages/web3-shared/evm/utils/index.ts
index 5d5011871555..a74ca021754b 100644
--- a/packages/web3-shared/evm/utils/index.ts
+++ b/packages/web3-shared/evm/utils/index.ts
@@ -7,5 +7,4 @@ export * from './chainDetailed'
export * from './transaction'
export * from './domain'
export * from './payload'
-export * from './misc'
export * from './provider'
diff --git a/packages/web3-shared/flow/tsconfig.json b/packages/web3-shared/flow/tsconfig.json
index b5b903e4be17..10de013f5120 100644
--- a/packages/web3-shared/flow/tsconfig.json
+++ b/packages/web3-shared/flow/tsconfig.json
@@ -6,5 +6,5 @@
"tsBuildInfoFile": "../dist/flow.tsbuildinfo"
},
"include": ["./", "./**/*.json"],
- "references": [{ "path": "../../web3-shared/base" }]
+ "references": [{ "path": "../base" }]
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e7dfa2ba128d..7a0111f56c1b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -12,7 +12,7 @@ importers:
'@commitlint/cli': ^16.2.3
'@commitlint/config-conventional': ^16.2.1
'@dimensiondev/eslint-plugin': ^0.0.1-20220117062517-fd3cb01
- '@dimensiondev/kit': 0.0.0-20220223101101-4e6f3b9
+ '@dimensiondev/kit': 0.0.0-20220228054820-f2378be
'@dimensiondev/patch-package': ^6.5.0
'@emotion/cache': ^11.7.1
'@emotion/react': ^11.8.2
@@ -61,7 +61,7 @@ importers:
web3-core: 1.5.2
web3-core-method: 1.5.2
dependencies:
- '@dimensiondev/kit': 0.0.0-20220223101101-4e6f3b9
+ '@dimensiondev/kit': 0.0.0-20220228054820-f2378be
'@emotion/cache': 11.7.1
'@emotion/react': 11.8.2_e9d1a1f7836a0632db868fe80683b80d
'@emotion/serialize': 1.0.2
@@ -136,6 +136,7 @@ importers:
'@masknet/plugin-example': workspace:*
'@masknet/plugin-flow': workspace:*
'@masknet/plugin-infra': workspace:*
+ '@masknet/plugin-solana': workspace:*
'@masknet/plugin-wallet': workspace:*
'@masknet/public-api': workspace:*
'@masknet/shared': workspace:*
@@ -190,6 +191,7 @@ importers:
'@masknet/plugin-example': link:../plugins/example
'@masknet/plugin-flow': link:../plugins/Flow
'@masknet/plugin-infra': link:../plugin-infra
+ '@masknet/plugin-solana': link:../plugins/Solana
'@masknet/plugin-wallet': link:../plugins/Wallet
'@masknet/public-api': link:../public-api
'@masknet/shared': link:../shared
@@ -307,12 +309,14 @@ importers:
'@masknet/gun-utils': workspace:*
'@masknet/icons': workspace:*
'@masknet/injected-script': workspace:*
+ '@masknet/plugin-cross-chain-bridge': workspace:*
'@masknet/plugin-cyberconnect': workspace:*
'@masknet/plugin-dao': workspace:*
'@masknet/plugin-debugger': workspace:*
'@masknet/plugin-example': workspace:*
'@masknet/plugin-file-service': workspace:*
'@masknet/plugin-flow': workspace:*
+ '@masknet/plugin-go-plus-security': workspace:*
'@masknet/plugin-infra': workspace:*
'@masknet/plugin-rss3': workspace:*
'@masknet/plugin-solana': workspace:*
@@ -445,12 +449,14 @@ importers:
'@masknet/gun-utils': link:../gun-utils
'@masknet/icons': link:../icons
'@masknet/injected-script': link:../injected-script
+ '@masknet/plugin-cross-chain-bridge': link:../plugins/CrossChainBridge
'@masknet/plugin-cyberconnect': link:../plugins/CyberConnect
'@masknet/plugin-dao': link:../plugins/DAO
'@masknet/plugin-debugger': link:../plugins/Debugger
'@masknet/plugin-example': link:../plugins/example
'@masknet/plugin-file-service': link:../plugins/FileService
'@masknet/plugin-flow': link:../plugins/Flow
+ '@masknet/plugin-go-plus-security': link:../plugins/GoPlusSecurity
'@masknet/plugin-infra': link:../plugin-infra
'@masknet/plugin-rss3': link:../plugins/RSS3
'@masknet/plugin-solana': link:../plugins/Solana
@@ -618,6 +624,18 @@ importers:
devDependencies:
react-i18next: 11.16.1_d4f4881e55b5d63494adb9d00faf9797
+ packages/plugins/CrossChainBridge:
+ specifiers:
+ '@masknet/plugin-infra': workspace:*
+ '@masknet/shared': workspace:*
+ '@masknet/shared-base': workspace:*
+ react-use: ^17.3.2
+ dependencies:
+ '@masknet/plugin-infra': link:../../plugin-infra
+ '@masknet/shared': link:../../shared
+ '@masknet/shared-base': link:../../shared-base
+ react-use: 17.3.2_581fa33d3647c30d8078674dcd1b240a
+
packages/plugins/CyberConnect:
specifiers:
'@cyberlab/cyberconnect': ^4.2.2
@@ -754,6 +772,32 @@ importers:
bignumber.js: 9.0.2
react-use: 17.3.2_581fa33d3647c30d8078674dcd1b240a
+ packages/plugins/GoPlusSecurity:
+ specifiers:
+ '@masknet/icons': workspace:*
+ '@masknet/plugin-infra': workspace:*
+ '@masknet/shared': workspace:*
+ '@masknet/shared-base': workspace:*
+ '@masknet/shared-base-ui': workspace:*
+ '@masknet/theme': workspace:*
+ '@masknet/web3-providers': workspace:*
+ '@masknet/web3-shared-evm': workspace:*
+ react-feather: ^2.0.9
+ react-use: ^17.3.2
+ urlcat: ^2.0.4
+ dependencies:
+ '@masknet/icons': link:../../icons
+ '@masknet/plugin-infra': link:../../plugin-infra
+ '@masknet/shared': link:../../shared
+ '@masknet/shared-base': link:../../shared-base
+ '@masknet/shared-base-ui': link:../../shared-base-ui
+ '@masknet/theme': link:../../theme
+ '@masknet/web3-providers': link:../../web3-providers
+ '@masknet/web3-shared-evm': link:../../web3-shared/evm
+ react-feather: 2.0.9_react@18.0.0-rc.3
+ react-use: 17.3.2_581fa33d3647c30d8078674dcd1b240a
+ urlcat: 2.0.4
+
packages/plugins/RSS3:
specifiers:
'@masknet/icons': workspace:*
@@ -971,6 +1015,7 @@ importers:
'@masknet/icons': workspace:*
'@masknet/plugin-infra': workspace:*
'@masknet/shared-base': workspace:*
+ '@masknet/shared-base-ui': workspace:*
'@masknet/theme': workspace:*
'@masknet/web3-shared-base': workspace:*
'@masknet/web3-shared-evm': workspace:*
@@ -983,13 +1028,13 @@ importers:
react-feather: ^2.0.9
react-use: ^17.3.2
urlcat: ^2.0.4
- use-subscription: ^1.5.1
- uuid: ^8.3.2
+ use-subscription: ^1.6.0
dependencies:
'@dimensiondev/holoflows-kit': 0.9.0-20210902104757-7c3d0d0
'@masknet/icons': link:../icons
'@masknet/plugin-infra': link:../plugin-infra
'@masknet/shared-base': link:../shared-base
+ '@masknet/shared-base-ui': link:../shared-base-ui
'@masknet/theme': link:../theme
'@masknet/web3-shared-base': link:../web3-shared/base
'@masknet/web3-shared-evm': link:../web3-shared/evm
@@ -1002,12 +1047,12 @@ importers:
react-feather: 2.0.9_react@18.0.0-rc.3
react-use: 17.3.2_581fa33d3647c30d8078674dcd1b240a
urlcat: 2.0.4
- use-subscription: 1.5.1_react@18.0.0-rc.3
- uuid: 8.3.2
+ use-subscription: 1.6.0_react@18.0.0-rc.3
packages/shared-base:
specifiers:
'@dimensiondev/holoflows-kit': ^0.9.0-20210902104757-7c3d0d0
+ '@masknet/public-api': workspace:*
'@masknet/typed-message': workspace:*
'@msgpack/msgpack': ^2.7.2
'@mui/icons-material': link:..\empty
@@ -1030,6 +1075,7 @@ importers:
z-schema: ^5.0.2
dependencies:
'@dimensiondev/holoflows-kit': 0.9.0-20210902104757-7c3d0d0
+ '@masknet/public-api': link:../public-api
'@masknet/typed-message': link:../typed-message
'@msgpack/msgpack': 2.7.2
'@mui/icons-material': link:../empty
@@ -1055,14 +1101,18 @@ importers:
specifiers:
'@dimensiondev/holoflows-kit': ^0.9.0-20210902104757-7c3d0d0
'@masknet/shared-base': workspace:*
+ '@types/use-subscription': ^1.0.0
+ '@types/uuid': ^8.3.4
react-use: ^17.3.2
- use-subscription: ^1.5.1
+ use-subscription: ^1.6.0
uuid: ^8.3.2
dependencies:
'@dimensiondev/holoflows-kit': 0.9.0-20210902104757-7c3d0d0
'@masknet/shared-base': link:../shared-base
+ '@types/use-subscription': 1.0.0
+ '@types/uuid': 8.3.4
react-use: 17.3.2_581fa33d3647c30d8078674dcd1b240a
- use-subscription: 1.5.1_react@18.0.0-rc.3
+ use-subscription: 1.6.0_react@18.0.0-rc.3
uuid: 8.3.2
packages/storybook-shared:
@@ -1080,6 +1130,7 @@ importers:
specifiers:
'@babel/core': ^7.17.8
'@masknet/icons': workspace:*
+ '@masknet/shared-base': workspace:*
'@masknet/storybook-shared': workspace:*
'@storybook/addon-actions': ^6.4.19
'@storybook/addon-essentials': ^6.4.19
@@ -1100,6 +1151,7 @@ importers:
use-subscription: ^1.5.1
dependencies:
'@masknet/icons': link:../icons
+ '@masknet/shared-base': link:../shared-base
'@masknet/storybook-shared': link:../storybook-shared
'@types/qrcode': 1.4.2
'@types/tinycolor2': 1.4.3
@@ -4359,13 +4411,6 @@ packages:
source-map-support: 0.5.21
dev: true
- /@babel/runtime/7.15.4:
- resolution: {integrity: sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==}
- engines: {node: '>=6.9.0'}
- dependencies:
- regenerator-runtime: 0.13.9
- dev: false
-
/@babel/runtime/7.16.3:
resolution: {integrity: sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==}
engines: {node: '>=6.9.0'}
@@ -5161,8 +5206,8 @@ packages:
webextension-polyfill: 0.8.0
dev: false
- /@dimensiondev/kit/0.0.0-20220223101101-4e6f3b9:
- resolution: {integrity: sha512-xk9rACwU2Iu+81BfnL1ykGofG+HuR/aMmYo4GZ1zyv32vZwe73B21z1GNGLq6XbYVjqNGa3puWoHZwDizAP4SA==, tarball: download/@dimensiondev/kit/0.0.0-20220223101101-4e6f3b9/a1b996a27d8d62c75d54a4a9d6b6544b45fe464288c83df31fccbbd2685b3a54}
+ /@dimensiondev/kit/0.0.0-20220228054820-f2378be:
+ resolution: {integrity: sha512-B+5bj3+nTqwp4nigrwXxKK3iQeCn1tlFOuYeqzPCJY/wqMrM+GsAyYdw5BPjFwn5+9uiZ5xSZcnLhSF/6EgjhQ==, tarball: download/@dimensiondev/kit/0.0.0-20220228054820-f2378be/8352c3f70e1a38376b321dd05989ad48c80a260785561da3910520e28560b26b}
dependencies:
lodash-es: 4.17.21
dev: false
@@ -17378,7 +17423,7 @@ packages:
engines: {node: '>=0.4.7'}
hasBin: true
dependencies:
- minimist: 1.2.5
+ minimist: 1.2.6
neo-async: 2.6.2
source-map: 0.6.1
wordwrap: 1.0.0
@@ -20702,7 +20747,6 @@ packages:
/minimist/1.2.6:
resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
- dev: false
/minipass-collect/1.0.2:
resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
@@ -20995,7 +21039,7 @@ packages:
react-dom: '*'
dependencies:
css-tree: 1.1.3
- csstype: 3.0.10
+ csstype: 3.0.11
fastest-stable-stringify: 2.0.2
inline-style-prefixer: 6.0.1
react: 18.0.0-rc.3
@@ -22952,10 +22996,10 @@ packages:
react: '>=16.6.0'
react-dom: '>=16.6.0'
dependencies:
- '@babel/runtime': 7.15.4
+ '@babel/runtime': 7.17.2
dom-helpers: 5.2.1
loose-envify: 1.4.0
- prop-types: 15.7.2
+ prop-types: 15.8.1
react: 18.0.0-rc.3
react-dom: 18.0.0-rc.3_react@18.0.0-rc.3
dev: false
@@ -23427,7 +23471,7 @@ packages:
safe-buffer: 5.2.1
tough-cookie: 2.5.0
tunnel-agent: 0.6.0
- uuid: 3.3.2
+ uuid: 3.4.0
dev: false
/require-directory/2.1.1:
@@ -23701,7 +23745,7 @@ packages:
/rtl-css-js/1.15.0:
resolution: {integrity: sha512-99Cu4wNNIhrI10xxUaABHsdDqzalrSRTie4GeCmbGVuehm4oj+fIy8fTzB+16pmKe8Bv9rl+hxIBez6KxExTew==}
dependencies:
- '@babel/runtime': 7.17.2
+ '@babel/runtime': 7.17.8
dev: false
/run-parallel/1.2.0:
@@ -26169,6 +26213,14 @@ packages:
react: 18.0.0-rc.3
dev: false
+ /use-subscription/1.6.0_react@18.0.0-rc.3:
+ resolution: {integrity: sha512-0Y/cTLlZfw547tJhJMoRA16OUbVqRm6DmvGpiGbmLST6BIA5KU5cKlvlz8DVMrACnWpyEjCkgmhLatthP4jUbA==}
+ peerDependencies:
+ react: ^18.0.0
+ dependencies:
+ react: 18.0.0-rc.3
+ dev: false
+
/use/3.1.1:
resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
engines: {node: '>=0.10.0'}
diff --git a/tsconfig.json b/tsconfig.json
index 9d256fb75970..712d80c6714b 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -99,6 +99,8 @@
"@masknet/plugin-solana": ["./packages/plugins/Solana/src"],
"@masknet/plugin-template": ["./packages/plugins/template/src"],
"@masknet/plugin-cyberconnect": ["./packages/plugins/CyberConnect/src"],
+ "@masknet/plugin-go-plus-security": ["./packages/plugins/GoPlusSecurity/src"],
+ "@masknet/plugin-cross-chain-bridge": ["./packages/plugins/CrossChainBridge/src"],
// @masknet/scripts: insert-here 3
"@masknet/plugin-wallet": ["./packages/plugins/Wallet/src"]
},