diff --git a/.i18n-codegen.json b/.i18n-codegen.json
index 0e88dbae02d2..cb999243cc8d 100644
--- a/.i18n-codegen.json
+++ b/.i18n-codegen.json
@@ -181,6 +181,18 @@
"trans": "Translate",
"sourceMap": "inline"
}
+ },
+ {
+ "input": "./packages/plugins/GoPlusSecurity/src/locales/en-US.json",
+ "output": "./packages/plugins/GoPlusSecurity/src/locales/i18n_generated",
+ "parser": "i18next",
+ "generator": {
+ "type": "i18next/react-hooks",
+ "hooks": "useI18N",
+ "namespace": "io.gopluslabs.security",
+ "trans": "Translate",
+ "sourceMap": "inline"
+ }
}
]
}
diff --git a/cspell.json b/cspell.json
index a95e6fc19fa4..35bc186fb02e 100644
--- a/cspell.json
+++ b/cspell.json
@@ -309,6 +309,7 @@
"gamification",
"getdodoroute",
"getx",
+ "gopluslabs",
"gorli",
"gusd",
"gwapj",
@@ -347,6 +348,7 @@
"nyfi",
"onflow",
"openocean",
+ "pausable",
"pkts",
"pnpm",
"poap",
diff --git a/packages/icons/general/Risk.tsx b/packages/icons/general/Risk.tsx
index ec4879d374e8..954ea2825e52 100644
--- a/packages/icons/general/Risk.tsx
+++ b/packages/icons/general/Risk.tsx
@@ -5,11 +5,11 @@ export const RiskIcon = createIcon(
,
'0 0 24 24',
diff --git a/packages/mask/.webpack/config.ts b/packages/mask/.webpack/config.ts
index e5a143627592..8fe5fbdd2ac9 100644
--- a/packages/mask/.webpack/config.ts
+++ b/packages/mask/.webpack/config.ts
@@ -88,6 +88,7 @@ export function createConfiguration(rawFlags: BuildFlags): Configuration {
'@masknet/plugin-wallet': join(__dirname, '../../plugins/Wallet/src/'),
'@masknet/plugin-file-service': join(__dirname, '../../plugins/FileService/src/'),
'@masknet/plugin-cyberconnect': join(__dirname, '../../plugins/CyberConnect/src/'),
+ '@masknet/plugin-go-plus-security': join(__dirname, '../../plugins/GoPlusSecurity/src/'),
'@masknet/external-plugin-previewer': join(__dirname, '../../external-plugin-previewer/src/'),
'@masknet/public-api': join(__dirname, '../../public-api/src/'),
'@masknet/sdk': join(__dirname, '../../mask-sdk/server/'),
diff --git a/packages/mask/package.json b/packages/mask/package.json
index 46985cd97798..a209f3631107 100644
--- a/packages/mask/package.json
+++ b/packages/mask/package.json
@@ -29,6 +29,7 @@
"@masknet/plugin-rss3": "workspace:*",
"@masknet/plugin-solana": "workspace:*",
"@masknet/plugin-wallet": "workspace:*",
+ "@masknet/plugin-go-plus-security": "workspace:*",
"@masknet/public-api": "workspace:*",
"@masknet/sdk": "workspace:*",
"@masknet/shared": "workspace:*",
diff --git a/packages/mask/src/plugin-infra/register.js b/packages/mask/src/plugin-infra/register.js
index 46f4973e183a..5cbbf531805e 100644
--- a/packages/mask/src/plugin-infra/register.js
+++ b/packages/mask/src/plugin-infra/register.js
@@ -10,6 +10,7 @@ import '@masknet/plugin-rss3'
import '@masknet/plugin-dao'
import '@masknet/plugin-solana'
import '@masknet/plugin-cyberconnect'
+import '@masknet/plugin-go-plus-security'
import '../plugins/Wallet'
import '../plugins/EVM'
import '../plugins/RedPacket'
diff --git a/packages/plugin-infra/src/types.ts b/packages/plugin-infra/src/types.ts
index 9b2209b1bf7d..e0bc35cd42ec 100644
--- a/packages/plugin-infra/src/types.ts
+++ b/packages/plugin-infra/src/types.ts
@@ -688,6 +688,7 @@ export enum PluginId {
UnlockProtocol = 'com.maskbook.unlockprotocol',
FileService = 'com.maskbook.fileservice',
CyberConnect = 'me.cyberconnect.app',
+ GoPlusSecurity = 'io.gopluslabs.security',
// @masknet/scripts: insert-here
}
diff --git a/packages/plugins/GoPlusSecurity/package.json b/packages/plugins/GoPlusSecurity/package.json
new file mode 100644
index 000000000000..378301090576
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/package.json
@@ -0,0 +1,20 @@
+{
+ "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",
+ "bignumber.js": "^9.0.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..df7c2abf0130
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/Common.tsx
@@ -0,0 +1,52 @@
+import type { SecurityAPI } from '@masknet/web3-providers'
+import { InfoIcon, 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)',
+ 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..89b95d44e949
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/NotFound.tsx
@@ -0,0 +1,13 @@
+import { Stack, Typography } from '@mui/material'
+import { useI18N } from '../../locales'
+
+export const NotFound = () => {
+ const t = useI18N()
+ return (
+
+ t.palette.error.main}>
+ {t.not_found_tip()}
+
+
+ )
+}
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 (
+
+ )
+ }) ?? []
+ )
+ }, [supportedChains.length])
+
+ return (
+
+
+
+
+
+
+ {
+ if (e.key !== 'Enter') return
+ onSearch(selectedChain?.id ?? DEFAULT_SEARCH_CHAIN, searchContent ?? '')
+ }}
+ onChange={(e) => setSearchSearchContent(e.target.value)}
+ InputProps={{
+ classes: { root: classes.search },
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+
+ {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..5ea7d29b7c1a
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/SecurityPanel.tsx
@@ -0,0 +1,148 @@
+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)}
+
+ {t.risky_factors({ quantity: riskyFactors.toString() })}
+
+
+ )}
+ {attentionFactors !== 0 && (
+
+ {DefineMapping[SecurityMessageLevel.Medium].icon(14)}
+
+ {t.attention_factors({ 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..fc17746b810f
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/components/TokenPanel.tsx
@@ -0,0 +1,170 @@
+import { Link, Stack, Tooltip, Typography } from '@mui/material'
+import { DefineMapping, SecurityMessageLevel, TokenSecurity } from './Common'
+import parseInt from 'lodash-es/parseInt'
+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 BigNumber from 'bignumber.js'
+import {
+ ERC20Token,
+ 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 new BigNumber(total).toFormat(3)
+}
+
+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..0f66eb822cc7
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/rules.ts
@@ -0,0 +1,283 @@
+import { SecurityMessageLevel, TokenSecurity } from './components/Common'
+import type { useI18N } from '../locales'
+import parseInt from 'lodash-es/parseInt'
+
+export type I18nOptions = 'rate' | 'quantity'
+
+export interface SecurityMessage {
+ type: 'contract-security' | 'transaction-security'
+ 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
+
+export const SecurityMessages: SecurityMessage[] = [
+ // open source
+ {
+ type: 'contract-security',
+ 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: (info: TokenSecurity) => info.is_open_source === undefined,
+ },
+ {
+ type: 'contract-security',
+ 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: (info: TokenSecurity) => info.is_open_source === undefined,
+ },
+ // proxy
+ {
+ type: 'contract-security',
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.is_proxy === '1',
+ titleKey: 'risk_proxy_contract_title',
+ messageKey: 'risk_proxy_contract_body',
+ shouldHide: (info: TokenSecurity) => info.is_proxy === undefined,
+ },
+ {
+ type: 'contract-security',
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.is_proxy === '0',
+ titleKey: 'risk_no_proxy_title',
+ messageKey: 'risk_no_proxy_body',
+ shouldHide: (info: TokenSecurity) => info.is_proxy === undefined,
+ },
+ // mint
+ {
+ type: 'contract-security',
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.is_mintable === '0',
+ titleKey: 'risk_no_mint_function_title',
+ messageKey: 'risk_no_mint_function_body',
+ shouldHide: (info: TokenSecurity) => info.is_mintable === undefined,
+ },
+ {
+ type: 'contract-security',
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.is_mintable === '1',
+ titleKey: 'risk_mint_function_title',
+ messageKey: 'risk_mint_function_body',
+ shouldHide: (info: TokenSecurity) => info.is_mintable === undefined,
+ },
+ // owner change balance
+ {
+ type: 'contract-security',
+ 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: (info: TokenSecurity) => info.owner_change_balance === undefined,
+ },
+ {
+ type: 'contract-security',
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.owner_change_balance === '1',
+ titleKey: 'risk_owner_change_balance_title',
+ messageKey: 'risk_owner_change_balance_body',
+ shouldHide: (info: TokenSecurity) => info.owner_change_balance === undefined,
+ },
+ // can take back ownership
+ {
+ type: 'contract-security',
+ 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: (info: TokenSecurity) => info.can_take_back_ownership === undefined,
+ },
+ {
+ type: 'contract-security',
+ 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: (info: TokenSecurity) => info.can_take_back_ownership === undefined,
+ },
+ // buy tax
+ {
+ type: 'transaction-security',
+ 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: (info: TokenSecurity) => info.buy_tax === undefined,
+ },
+ {
+ type: 'contract-security',
+ 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: (info: TokenSecurity) => info.buy_tax === undefined,
+ },
+ {
+ type: 'contract-security',
+ 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: (info: TokenSecurity) => info.buy_tax === undefined,
+ },
+ // sell tax
+ {
+ type: 'transaction-security',
+ 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: (info: TokenSecurity) => info.sell_tax === undefined,
+ },
+ {
+ type: 'contract-security',
+ 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: (info: TokenSecurity) => info.sell_tax === undefined,
+ },
+ {
+ type: 'contract-security',
+ 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: (info: TokenSecurity) => info.sell_tax === undefined,
+ },
+ // honeypot
+ {
+ type: 'transaction-security',
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.is_honeypot === '0',
+ titleKey: 'risk_is_not_honeypot_title',
+ messageKey: 'risk_is_not_honeypot_body',
+ shouldHide: (info: TokenSecurity) => info.is_honeypot === undefined,
+ },
+ {
+ type: 'contract-security',
+ level: SecurityMessageLevel.High,
+ condition: (info: TokenSecurity) => info.is_honeypot === '1',
+ titleKey: 'risk_is_honeypot_title',
+ messageKey: 'risk_is_honeypot_body',
+ shouldHide: (info: TokenSecurity) => info.is_honeypot === undefined,
+ },
+ // transfer_pausable
+ {
+ type: 'transaction-security',
+ 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: (info: TokenSecurity) => info.transfer_pausable === undefined,
+ },
+ {
+ type: 'contract-security',
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.transfer_pausable === '1',
+ titleKey: 'risk_transfer_pausable_title',
+ messageKey: 'risk_transfer_pausable_body',
+ shouldHide: (info: TokenSecurity) => info.transfer_pausable === undefined,
+ },
+ // anti whale
+ {
+ type: 'transaction-security',
+ 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: (info: TokenSecurity) => info.is_anti_whale === undefined,
+ },
+ {
+ type: 'contract-security',
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.is_anti_whale === '1',
+ titleKey: 'risk_is_anti_whale_title',
+ messageKey: 'risk_is_anti_whale_body',
+ shouldHide: (info: TokenSecurity) => info.is_anti_whale === undefined,
+ },
+ // slippage modifiable
+ {
+ type: 'transaction-security',
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.slippage_modifiable === '0',
+ titleKey: 'risk_not_slippage_modifiable_title',
+ messageKey: 'risk_not_slippage_modifiable_body',
+ shouldHide: (info: TokenSecurity) => info.slippage_modifiable === undefined,
+ },
+ {
+ type: 'contract-security',
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.slippage_modifiable === '1',
+ titleKey: 'risk_slippage_modifiable_title',
+ messageKey: 'risk_slippage_modifiable_body',
+ shouldHide: (info: TokenSecurity) => info.slippage_modifiable === undefined,
+ },
+ // black list
+ {
+ type: 'transaction-security',
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.is_blacklisted === '0',
+ titleKey: 'risk_not_is_blacklisted_title',
+ messageKey: 'risk_not_is_blacklisted_body',
+ shouldHide: (info: TokenSecurity) => info.is_blacklisted === undefined,
+ },
+ {
+ type: 'contract-security',
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.is_blacklisted === '1',
+ titleKey: 'risk_is_blacklisted_title',
+ messageKey: 'risk_is_blacklisted_body',
+ shouldHide: (info: TokenSecurity) => info.is_blacklisted === undefined,
+ },
+ // white list
+ {
+ type: 'transaction-security',
+ level: SecurityMessageLevel.Safe,
+ condition: (info: TokenSecurity) => info.is_whitelisted === '0',
+ titleKey: 'risk_not_is_whitelisted_title',
+ messageKey: 'risk_not_is_whitelisted_body',
+ shouldHide: (info: TokenSecurity) => info.is_whitelisted === undefined,
+ },
+ {
+ type: 'contract-security',
+ level: SecurityMessageLevel.Medium,
+ condition: (info: TokenSecurity) => info.is_whitelisted === '1',
+ titleKey: 'risk_is_whitelisted_title',
+ messageKey: 'risk_is_whitelisted_body',
+ shouldHide: (info: TokenSecurity) => info.is_whitelisted === undefined,
+ },
+]
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..9f208758c4c5
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/locales/en-US.json
@@ -0,0 +1,74 @@
+{
+ "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",
+ "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": "Network error or the on-chain data is abnormal. Please switch to the correct network or check the address again.",
+ "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)."
+}
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..a1d47642fe4a
--- /dev/null
+++ b/packages/plugins/GoPlusSecurity/src/utils/helper.ts
@@ -0,0 +1,5 @@
+import urlcat from 'urlcat'
+
+export function resolveGoLabLink(chainId: string, 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/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/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 `