@particle-network/authkit
Version:
Auth Core provides MPC (Multi-Party Computation)-based threshold signatures.
4 lines • 194 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/pages/sign/index.tsx", "../../src/pages/sign/components/evm-sign.tsx", "../../src/context/hooks/usePending.tsx", "../../src/pages/sign/evmContextProvider.tsx", "../../src/utils/ethereumUtils.ts", "../../src/pages/sign/riskReminder/index.tsx", "../../src/pages/sign/riskTypography/index.tsx", "../../src/pages/sign/components/NewErcTransfers.tsx", "../../src/components/icon-edit-pen.tsx", "../../src/components/icon-to.tsx", "../../src/pages/sign/components/gas-display.tsx", "../../src/pages/sign/components/no-gas.tsx", "../../src/pages/sign/components/evm-gas.tsx"],
"sourcesContent": ["import { isNeedRestoreWallet, syncUserInfo } from '@particle-network/auth-core';\nimport React, { useEffect } from 'react';\nimport { AuthPage } from '../../components/customRouter';\nimport { useCustomNavigate } from '../../context';\nimport EvmSign from './components/evm-sign';\nimport { EVMContextProvider } from './evmContextProvider';\n\nexport type EvmSignProps = {\n method: string;\n param: unknown;\n loginAuthorizationSign?: boolean;\n};\n\nfunction Sign(props: EvmSignProps) {\n const navigate = useCustomNavigate();\n\n useEffect(() => {\n syncUserInfo().catch((e) => console.log('syncUserInfo', e));\n if (isNeedRestoreWallet()) {\n navigate(AuthPage.MasterPasswordVerify);\n }\n }, []);\n\n return (\n <EVMContextProvider {...props}>\n <EvmSign {...props} />\n </EVMContextProvider>\n );\n}\n\nexport default Sign;\n", "import { addHexPrefix, stripHexPrefix } from '@ethereumjs/util';\nimport { RecordType } from '@particle-network/analytics';\nimport {\n EvmEnhancedMethod,\n EvmRpcMethod,\n TransactionSmartType,\n analyticsRecord,\n defaultTokenIcon,\n getChainIcon,\n isConnected,\n syncUserInfo,\n type EVMDeserializeTransactionResult,\n type EVMFunction,\n type EVMNFTChange,\n type EVMTransaction,\n} from '@particle-network/auth-core';\nimport { Button, Image, Modal, Tabs } from 'antd';\nimport { BigNumber } from 'bignumber.js';\nimport BN from 'bn.js';\n//@ts-ignore\nimport jt from 'json-toy';\nimport React, { useEffect, useMemo, useRef, useState } from 'react';\nimport CopyToClipboard from 'react-copy-to-clipboard';\nimport { type EvmSignProps } from '..';\nimport { getEVMPublicAddress } from '../../../api/getPublicAddress';\nimport { AuthError } from '../../../api/model/authError';\nimport { PromptSettingType } from '../../../api/model/bundle';\nimport { AuthPage } from '../../../components/customRouter';\nimport CircleClose from '../../../components/icon/circle-close';\nimport IconCopy from '../../../components/icon/icon-copy';\nimport ParticleLoading from '../../../components/loading';\nimport PowerFooter from '../../../components/power-footer';\nimport { useAuthCoreModal, useCustomNavigate, useEvents, useParticleAuth, useTranslation } from '../../../context';\nimport { useEthereum } from '../../../context/hooks';\nimport { useError } from '../../../context/hooks/useError';\nimport useMessage from '../../../context/hooks/useMessage';\nimport { usePaymentPassword } from '../../../context/hooks/usePaymentPassword';\nimport usePending from '../../../context/hooks/usePending';\nimport { AuthCoreModalEvent } from '../../../context/types';\nimport { formatAddress, getChainDisplayName, getNativeSymbol } from '../../../utils/chain-utils';\nimport { shortString } from '../../../utils/common-utils';\nimport { ethereumUtils } from '../../../utils/ethereumUtils';\nimport { formatTokenAmount2, fromSunFormat, fromWeiFormat } from '../../../utils/number-utils';\nimport { isEIP1559Type, isEVMAddress, isTron, parseTransaction } from '../../../utils/transaction-utils';\nimport { useEVMTransaction } from '../evmContextProvider';\nimport riskModalStyle from '../riskModal/index.less';\nimport RiskReminder from '../riskReminder';\nimport RiskTypography from '../riskTypography';\nimport NewErcTransfers from './NewErcTransfers';\nimport EvmGas from './evm-gas';\nimport GasDisplay from './gas-display';\nimport styles from './info-sign.less';\nimport Menu from './menu';\nimport NoGas from './no-gas';\n\nimport biconomy000 from '../../../common/images/smartAccount/biconomy-000-icon.png';\nimport biconomyfff from '../../../common/images/smartAccount/biconomy-fff-icon.png';\nimport btc000 from '../../../common/images/smartAccount/btc-000-icon.png';\nimport btcfff from '../../../common/images/smartAccount/btc-fff-icon.png';\nimport cyberconnect000 from '../../../common/images/smartAccount/cyberconnect-000-icon.png';\nimport cyberconnectfff from '../../../common/images/smartAccount/cyberconnect-fff-icon.png';\nimport light000 from '../../../common/images/smartAccount/light-000-icon.png';\nimport lightfff from '../../../common/images/smartAccount/light-fff-icon.png';\nimport simple000 from '../../../common/images/smartAccount/simple-000-icon.png';\nimport simplefff from '../../../common/images/smartAccount/simple-fff-icon.png';\nimport universal000 from '../../../common/images/smartAccount/universal-000-icon.png';\nimport universalfff from '../../../common/images/smartAccount/universal-fff-icon.png';\nimport xterio from '../../../common/images/smartAccount/xterio-icon.png';\nimport { isTelegramWebApp } from '../../../utils/isTelegramWebApp';\n\nenum RenderPageType {\n SIGN_MESSAGE,\n SEND_TX,\n SIGN_TYPE_DATA,\n}\n\nexport const getAAIcon = (aaType: string, theme: string) => {\n aaType = (aaType ?? 'biconomy')?.toLowerCase();\n const aaIcons: Record<string, any> = {\n biconomyfff,\n biconomy000,\n cyberconnectfff,\n cyberconnect000,\n simplefff,\n simple000,\n lightfff,\n light000,\n btcfff,\n btc000,\n universal000,\n universalfff,\n xterio000: xterio,\n xteriofff: xterio,\n };\n const key = `${aaType}${theme === 'light' ? 'fff' : '000'}`;\n return aaIcons[key] ?? aaIcons['biconomyfff'];\n};\n\nfunction EvmSign(props: EvmSignProps) {\n const { method, param, loginAuthorizationSign } = props;\n\n const { t } = useTranslation();\n const message = useMessage();\n const [loading, setLoading] = useState(false);\n\n const [transactionInfo, setTransactionInfo] = useState<EVMDeserializeTransactionResult>();\n\n const infoSignRef = useRef(null);\n\n const [headerTitle, setHeaderTitle] = useState<string>('');\n const { authCoreModal } = useAuthCoreModal();\n const [headerDes, setHeaderDes] = useState<string>('');\n\n const [gasVis, setGasVis] = useState<boolean>(false);\n\n const navigate = useCustomNavigate();\n\n const [renderPageType, setRenderPageType] = useState<RenderPageType | ''>('');\n\n const [changeApproveAmount, setChangeApproveAmount] = useState<string>('');\n\n const { errorHandle } = useError();\n\n const { transactionData, gasError, setTransaction, updateTransaction, currentChain: chainInfo } = useEVMTransaction();\n\n const [riskPrompt, setRiskPrompt] = useState(false);\n\n const { events } = useEvents();\n\n const [addressDisplayed, setAddressDisplayed] = useState<string>();\n\n const { hasSetPaymentPassword, showSetPaymentPasswordOrConfirm } = usePaymentPassword();\n\n const { setPaymentVerify, setPaymentPassword, userInfo, modalOptions } = useParticleAuth();\n\n const { address } = useEthereum();\n\n const { checkPending } = usePending();\n\n const loadsecurityAccounts = () => {\n syncUserInfo()\n .then(() => {\n approveSign();\n })\n .catch((error) => {\n setLoading(false);\n message.error(error.message ?? 'Sign Error');\n });\n };\n\n const { TabPane } = Tabs;\n\n const isPersonalSign = useMemo(\n () => method === EvmRpcMethod.personalSign || method === EvmRpcMethod.personalSignUniq,\n [method]\n );\n\n const redirectToApp = ({ error, result }: { error?: AuthError; result?: unknown }) => {\n events.emit(AuthCoreModalEvent.SignResponse, {\n result,\n error,\n });\n };\n\n useEffect(() => {\n getEVMPublicAddress({\n erc4337: modalOptions.erc4337,\n chainId: chainInfo.id,\n method: method as EvmRpcMethod,\n })\n .then((addr) => {\n setAddressDisplayed(addr);\n })\n .catch((error) => {\n console.error('get public address error', error);\n });\n }, [chainInfo.id, modalOptions.erc4337, method]);\n\n useEffect(() => {\n let renderPageType: RenderPageType | '' = '';\n console.log(`evm sign method: ${method}, chainId: ${chainInfo.id}`);\n if (isPersonalSign) {\n //sign message\n setHeaderTitle(t('sign.signature_message') as string);\n setHeaderDes(t('sign.signature_title') as string);\n\n renderPageType = RenderPageType.SIGN_MESSAGE;\n } else if (method === EvmRpcMethod.ethSendTransaction) {\n //send tx\n setHeaderTitle(t('sign.send_transaction') as string);\n setHeaderDes(t('sign.approve_and').format(getChainDisplayName(chainInfo)));\n setTransactionData();\n renderPageType = RenderPageType.SEND_TX;\n } else if (method === EvmRpcMethod.ethSignTypedDataV4 || method === EvmRpcMethod.ethSignTypedDataV4Uniq) {\n deserializeTypedData(JSON.stringify(param)).then((res) => {\n if (res?.type === TransactionSmartType.SEAPORT_NFT_LISTING) {\n setHeaderTitle(t('sign.send_transaction') as string);\n setHeaderDes(t('sign.approve_and').format(getChainDisplayName(chainInfo)));\n renderPageType = RenderPageType.SEND_TX;\n } else {\n renderPageType = RenderPageType.SIGN_TYPE_DATA;\n setHeaderTitle(t('sign.sign_typed_data') as string);\n setHeaderDes(t('sign.signature_title') as string);\n }\n setRenderPageType(renderPageType);\n });\n } else if (method.includes(EvmRpcMethod.ethSignTypedData)) {\n //sign typed data\n setHeaderTitle(t('sign.sign_typed_data') as string);\n setHeaderDes(t('sign.signature_title') as string);\n renderPageType = RenderPageType.SIGN_TYPE_DATA;\n }\n\n setRenderPageType(renderPageType);\n }, [chainInfo, t]);\n\n const setTransactionData = () => {\n console.log('setTransactionData', param);\n const txData = parseTransaction(param as any, chainInfo);\n console.log('setTransactionData\uFF0C after parse', txData);\n setTransaction(txData);\n deserializeTransaction(txData);\n };\n\n const deserializeTypedData = async (jsonStr: string) => {\n const result = await window.particleAuth?.ethereum\n .request({\n chainId: chainInfo.id,\n method: EvmEnhancedMethod.deserializeTypedData,\n params: [jsonStr],\n })\n .catch((error) => {\n console.log('deserializeTypedData error', error);\n message.error(error.message ?? 'deserializeTypedData Error');\n });\n setTransactionInfo(result);\n return result;\n };\n\n const deserializeTransaction = async (txData: EVMTransaction) => {\n if (!checkTxData(txData)) {\n return;\n }\n\n const module = await import('@ethereumjs/tx');\n const { TransactionFactory } = module.default || module;\n const tx = TransactionFactory.fromTxData(txData);\n window.particleAuth?.ethereum\n .request({\n chainId: chainInfo.id,\n method: EvmEnhancedMethod.deserializeTransaction,\n params: [address, addHexPrefix(tx.serialize().toString('hex'))],\n })\n .then((result) => {\n setTransactionInfo(result);\n })\n .catch((error) => {\n console.log('deserializeTransaction error', error);\n Modal.error({\n title: error.message ?? 'Deserialize Transaction Error',\n wrapClassName: 'auth-core-modal-error',\n getContainer: () => {\n return authCoreModal.rootBody as HTMLElement;\n },\n okCancel: true,\n cancelText: t('common.cancel'),\n okText: t('common.retry'),\n onOk: () => {\n deserializeTransaction(txData);\n },\n });\n });\n };\n\n const hasSecurityRisk = useMemo(() => {\n return transactionInfo?.securityDetection && transactionInfo?.securityDetection.length > 0;\n }, [transactionInfo?.securityDetection]);\n\n const checkTxData = (txData: EVMTransaction): boolean => {\n if (isTron(chainInfo)) {\n if (!txData.from || !txData.to || !txData.value) {\n Modal.error({\n title: 'Transaction error, see doc for more info',\n wrapClassName: 'auth-core-modal-error',\n getContainer: () => {\n return authCoreModal.rootBody as HTMLElement;\n },\n onOk: () => {\n redirectToApp({\n error: AuthError.paramsError(),\n });\n },\n });\n return false;\n }\n\n return true;\n }\n if (Number(txData.type) !== 0 && Number(txData.type) !== 1 && Number(txData.type) !== 2) {\n Modal.error({\n title: 'Transaction type error, see doc for more info',\n wrapClassName: 'auth-core-modal-error',\n getContainer: () => {\n return authCoreModal.rootBody as HTMLElement;\n },\n onOk: () => {\n redirectToApp({\n error: AuthError.paramsError(),\n });\n },\n });\n return false;\n } else if (isEIP1559Type(txData.type) && txData.maxFeePerGas && txData.maxPriorityFeePerGas) {\n const maxFeePerGasBN = new BN(stripHexPrefix(txData.maxFeePerGas), 16);\n const maxPriorityFeePerGasBN = new BN(stripHexPrefix(txData.maxPriorityFeePerGas), 16);\n if (maxFeePerGasBN.lte(maxPriorityFeePerGasBN)) {\n // maxFeePerGas cannot be less than maxPriorityFeePerGas\n Modal.error({\n title: 'maxFeePerGas cannot be less than maxPriorityFeePerGas.',\n wrapClassName: 'auth-core-modal-error',\n getContainer: () => {\n return authCoreModal.rootBody as HTMLElement;\n },\n onOk: () => {\n redirectToApp({\n error: AuthError.feeError(),\n });\n },\n });\n return false;\n }\n }\n return true;\n };\n\n const decodeMessage = (message: string): string => {\n const msg = Buffer.from(stripHexPrefix(message), 'hex').toString('utf-8');\n if (isPersonalSign) {\n if (/\uFFFD/.test(msg)) {\n return message;\n }\n }\n return msg;\n };\n\n const formatFunction = (evmFunction: EVMFunction): string => {\n if (evmFunction.params.length > 0) {\n const p = evmFunction.params.map((item) => item.type).join(', ');\n return `${evmFunction.name}(${p})`;\n }\n return `${evmFunction.name}()`;\n };\n\n const approveSign = async (pendingConfirm = false) => {\n if (!isConnected()) {\n redirectToApp({\n error: AuthError.notLogin(),\n });\n return;\n }\n\n analyticsRecord({\n // eslint-disable-next-line camelcase\n record_type: RecordType.PAGE_SIGN_CONFIRM_BUTTON_CLICK, // confirm\u6309\u94AE\u70B9\u51FB\n });\n\n if (method === EvmRpcMethod.ethSendTransaction) {\n if (await checkPending(approveSign, pendingConfirm)) {\n return;\n } else if (userInfo?.security_account?.has_set_payment_password) {\n setPaymentVerify({\n visible: true,\n onVerifyCompleted: signTx,\n });\n } else {\n showSetPaymentPasswordOrConfirm(signTx);\n }\n } else if (method.includes(EvmRpcMethod.ethSignTypedData) || isPersonalSign) {\n if (userInfo?.security_account?.has_set_payment_password) {\n setPaymentVerify({\n visible: true,\n onVerifyCompleted: signData,\n });\n } else {\n if (\n method.includes(EvmRpcMethod.ethSignTypedData) ||\n modalOptions.promptSettingConfig?.promptPaymentPasswordSettingWhenSign === PromptSettingType.everyAndNotSkip\n ) {\n // method\u662Feth_signTypedData \u6216\u8005 \u5F3A\u5236\u8BBE\u7F6E\u652F\u4ED8\u5BC6\u7801\n showSetPaymentPasswordOrConfirm(signData);\n } else {\n signData();\n }\n }\n } else {\n Modal.error({\n title: `method ${method} not support`,\n wrapClassName: 'auth-core-modal-error',\n getContainer: () => {\n return authCoreModal.rootBody as HTMLElement;\n },\n });\n }\n };\n\n const signTx = async () => {\n if (!transactionData) return;\n\n setLoading(true);\n\n const unsigned = JSON.stringify(transactionData);\n console.log('unsigned tx:', unsigned);\n\n let result;\n try {\n result = await window.particleAuth?.ethereum.sendTransaction({ ...transactionData });\n analyticsRecord({\n // eslint-disable-next-line camelcase\n record_type: RecordType.PAGE_SIGN_CONFIRM_BUTTON_CLICK_SUCCESS, // confirm\u6210\u529F\n });\n } catch (error: any) {\n console.error(method, error);\n analyticsRecord({\n // eslint-disable-next-line camelcase\n record_type: RecordType.PAGE_SIGN_CONFIRM_BUTTON_CLICK_FAILURE, // confirm\u5931\u8D25\n });\n if (error?.error_code === 50103 && !userInfo?.security_account?.has_set_payment_password) {\n loadsecurityAccounts();\n } else if (error?.message === 'Local Key not found' || error?.message === 'Master password decryption error') {\n navigate(AuthPage.MasterPasswordVerify);\n } else {\n Modal.error({\n title: error.message ?? 'Send Transaction Error',\n wrapClassName: 'auth-core-modal-error',\n getContainer: () => {\n return authCoreModal.rootBody as HTMLElement;\n },\n okText: t('common.confirm'),\n onOk: () => {\n redirectToApp({\n error: error,\n });\n },\n });\n }\n } finally {\n setLoading(false);\n }\n if (result) {\n redirectToApp({\n result,\n });\n }\n };\n\n const signData = async () => {\n setLoading(true);\n let result;\n try {\n if (isPersonalSign) {\n result = await window.particleAuth?.ethereum.signMessage(param as string);\n } else {\n result = await window.particleAuth?.ethereum.signTypedData({\n data: param as any,\n version: ethereumUtils.getSignTypedVersion(method),\n uniq: ethereumUtils.isSignTypedDataUniq(method),\n });\n }\n analyticsRecord({\n // eslint-disable-next-line camelcase\n record_type: RecordType.PAGE_SIGN_CONFIRM_BUTTON_CLICK_SUCCESS, // confirm\u6210\u529F\n });\n } catch (error: any) {\n console.error(method, error);\n analyticsRecord({\n // eslint-disable-next-line camelcase\n record_type: RecordType.PAGE_SIGN_CONFIRM_BUTTON_CLICK_FAILURE, // confirm\u5931\u8D25\n });\n if (error?.error_code === 50103 && !userInfo?.security_account?.has_set_payment_password) {\n loadsecurityAccounts();\n } else if (error?.message === 'Local Key not found' || error?.message === 'Master password decryption error') {\n navigate(AuthPage.MasterPasswordVerify);\n } else {\n message.error(error.message ?? (isPersonalSign ? 'Sign Message Error' : 'Sign Typed Data Error'));\n }\n } finally {\n setLoading(false);\n }\n\n if (result) {\n if (loginAuthorizationSign) {\n events.emit(AuthCoreModalEvent.LoginSuccess, {\n ...userInfo,\n authorization: {\n message: param,\n signature: result,\n },\n });\n } else {\n redirectToApp({\n result,\n });\n }\n }\n };\n\n const cancelSign = async () => {\n if (loading) {\n return;\n }\n\n if (loginAuthorizationSign) {\n events.emit(AuthCoreModalEvent.LoginSuccess, userInfo);\n } else {\n //\u8FD4\u56DEapp\n redirectToApp({\n error: AuthError.userRejectedRequest(),\n });\n }\n };\n\n const formatValue = (data: any) => {\n if (isEVMAddress(data.value)) {\n return shortString(formatAddress(data.value, chainInfo));\n }\n return data.value;\n };\n\n const signMessageTitle = useMemo(() => {\n let title = '';\n if (method.includes(EvmRpcMethod.ethSignTypedData)) {\n try {\n const { primaryType } = param as any;\n title = primaryType;\n } catch (error) {\n // pase error\n }\n }\n return title || 'Message';\n }, [method, param]);\n\n const sMessage = useMemo(() => {\n if (method.includes(EvmRpcMethod.ethSignTypedData)) {\n try {\n const signQueryMessage = param as any;\n let { message } = signQueryMessage;\n\n if (!signQueryMessage.message) {\n message = signQueryMessage;\n }\n\n return message;\n } catch (error) {\n // pase error\n }\n }\n return {};\n }, [method, param]);\n\n const getRow = (key: string, index: number) => {\n key = key.replace('ROOT.', '');\n let value = jt.getValByKeyPath(sMessage, key);\n let isTitle = false;\n if (typeof value !== 'string' && typeof value !== 'number') {\n value = '';\n isTitle = true;\n }\n const indent = key.split('.').length;\n\n return (\n <div key={index} className='s-row'>\n <div\n className='label'\n style={{\n paddingLeft: 20 * indent,\n }}\n data-type={isTitle ? 'title' : ''}\n >\n {key.split('.').pop()}\uFF1A\n </div>\n\n {isEVMAddress(value) ? (\n <CopyToClipboard text={value} onCopy={() => message.success(t('new.copied_to'))}>\n <div className='value copy-text'>{formatValue({ value })}</div>\n </CopyToClipboard>\n ) : (\n <div\n className='value'\n onClick={(e) => {\n e.stopPropagation();\n }}\n >\n {value}\n </div>\n )}\n </div>\n );\n };\n // sign message\n const signMessage = () => {\n return (\n <div className='sign-message'>\n <div\n className={'message' + (hasSetPaymentPassword ? '' : ' no-password-tip')}\n data-transaction-type={transactionInfo?.type}\n >\n {!method.includes(EvmRpcMethod.ethSignTypedData) && (\n <div className='pre-wrap personal-message'>{decodeMessage(param as string)}</div>\n )}\n\n {method.includes(EvmRpcMethod.ethSignTypedData) && (\n <>\n <div className='s-row'>\n <div className='label' data-type='title'>\n {signMessageTitle}\n </div>\n </div>\n {jt.travelJson(sMessage).map((key: string, index: number) => {\n return getRow(key, index);\n })}\n </>\n )}\n </div>\n </div>\n );\n };\n\n const getNFTName = (info: EVMNFTChange): string => {\n if (info.name && info.name.length > 0) {\n return info.name;\n }\n\n return `NFT#${info.tokenId}`;\n };\n\n const approveDisabled = (data?: EVMTransaction): boolean => {\n if (method === EvmRpcMethod.ethSendTransaction) {\n if (!data) {\n return true;\n }\n\n if (isTron(chainInfo)) {\n return false;\n }\n\n if (isEIP1559Type(data.type)) {\n return !data.gasLimit || !data.maxPriorityFeePerGas || !data.maxFeePerGas;\n } else {\n return !data.gasLimit || !data.gasPrice;\n }\n }\n return false;\n };\n const [displayDetail, setDisplayDetail] = useState<boolean>(false);\n\n const isErc4361 = useMemo(() => {\n // https://eips.ethereum.org/EIPS/eip-4361#example-message\n let result = false;\n if (isPersonalSign) {\n const signMessage = decodeMessage(param as string);\n const domain = signMessage.match(/^(.+)?\\swants you/)?.[1];\n const address = signMessage.match(/wants you to sign in with your Ethereum account:\\n(.*)/)?.[1];\n const uri = signMessage.match(/URI:(.*)/)?.[1];\n const version = signMessage.match(/Version:(.*)/)?.[1];\n const chainId = signMessage.match(/Chain ID:(.*)/)?.[1];\n const nonce = signMessage.match(/Nonce:(.*)/)?.[1];\n if (domain && address && uri && version && chainId && nonce) {\n result = true;\n }\n }\n return result;\n }, [transactionData, isPersonalSign]);\n\n useEffect(() => {\n if (isErc4361) {\n setHeaderTitle(t('new.sign_in_request') as string);\n setHeaderDes(t('new.requesting_sign_4361') as string);\n }\n }, [isErc4361]);\n\n // sign/send transaction\n const signTransaction = () => {\n return (\n <Tabs defaultActiveKey='1'>\n <TabPane tab={t('sign.details')} key='1'>\n <div className='balance-change'>\n <div className='title'>{t('sign.estimated_balance_change')}</div>\n <div className='change-body'>\n {transactionInfo?.estimatedChanges?.natives\n ?.filter((info) => info.address.toLowerCase() === address?.toLowerCase())\n ?.map((info, index) => {\n return (\n <div className='change-title' key={`native-change-${index}`}>\n {getNativeSymbol(chainInfo)}\n <div className='change-val' style={info.nativeChange.includes('-') ? { color: '#ea4335' } : {}}>\n {info.nativeChange.includes('-') || info.nativeChange === '0' ? '' : '+'}\n {isTron(chainInfo)\n ? fromSunFormat(info.nativeChange)\n : fromWeiFormat(info.nativeChange, 'ether', 18)}\n </div>\n </div>\n );\n })}\n\n {transactionInfo?.estimatedChanges?.nfts?.map((info, index) => {\n return (\n <div className='change-title' key={`nft-change-${index}`}>\n {getNFTName(info)}\n <div className='change-val' style={info.amountChange < 0 ? { color: '#ea4335' } : {}}>\n {info.amountChange < 0 ? '' : '+'}\n {info.amountChange}\n </div>\n </div>\n );\n })}\n\n {transactionInfo?.estimatedChanges?.tokens?.map((info, index) => {\n return (\n <div className='change-title' key={`token-change-${index}`}>\n {info.name ? info.name : 'Unknown Token'}\n <div className='change-val' style={info.amountChange < 0 ? { color: '#ea4335' } : {}}>\n {info.amountChange < 0 ? '' : '+'}\n {formatTokenAmount2(info.amountChange, info.decimals)}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n\n {transactionInfo && (\n <div className='from-to'>\n <div className='address-item'>\n <div>{t('sign.from')}</div>\n <div>{shortString(formatAddress(transactionInfo.data.from, chainInfo))}</div>\n </div>\n <div className='address-item mt10'>\n <div>{t('sign.to')}</div>\n <div>{shortString(formatAddress(transactionInfo.data.to, chainInfo))}</div>\n </div>\n {!isTron(chainInfo) && (\n <div className='address-item mt10'>\n <div>{t('sign.nonce')}</div>\n <div>#{parseInt(transactionInfo.data.nonce)}</div>\n </div>\n )}\n </div>\n )}\n\n {!gasError && transactionData && !isTron(chainInfo) && (\n <GasDisplay\n openGasDrawer={() => setGasVis(true)}\n signLoading={loading}\n chainInfo={chainInfo}\n signMethod={method}\n />\n )}\n\n {gasError && !isTron(chainInfo) && <NoGas />}\n </TabPane>\n <TabPane tab={t('sign.data')} key='2'>\n <div>\n {transactionInfo && transactionInfo.data.function && (\n <div className='inner-instruction' key={'instruction-function'}>\n <div className='inner-content'>\n <div className='content-item'>\n <div className='item'>\n <div className='item-0'>\n {t('sign.function_type')} {formatFunction(transactionInfo.data.function)}\n </div>\n\n {transactionInfo.data.function.params.map((item) => (\n <div className='item-1 mt10' key={`instruction-function${item.name}`}>\n {shortString(item.name)}\n <span>{shortString(item.value)}</span>\n </div>\n ))}\n </div>\n </div>\n </div>\n </div>\n )}\n\n {/* hex data */}\n\n {transactionInfo && (\n <div className='inner-instruction' key={'instruction-hex-data'}>\n <div className='inner-content'>\n <div className='content-item'>\n <div className='item'>\n <div className='item-0'>{t('sign.hex_data')}</div>\n <div className='item-1 mt10'>\n <div className='data'>{transactionInfo.data.data}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n )}\n </div>\n </TabPane>\n </Tabs>\n );\n };\n\n const editApproveAmount = async (amount: string) => {\n setChangeApproveAmount(amount);\n\n if (amount && amount !== '' && transactionInfo) {\n const { decimals = 18 } = transactionInfo?.estimatedChanges?.tokens?.[0] || {};\n\n BigNumber.config({ EXPONENTIAL_AT: [-256, 256] });\n const bn = new BigNumber(amount).multipliedBy(new BigNumber(10).pow(decimals));\n const approveAmount = bn.toString();\n BigNumber.config({ EXPONENTIAL_AT: [-7, 21] });\n\n const spender = transactionInfo.data?.function?.params?.[0]?.value || '';\n\n const encodeData = await window.particleAuth?.ethereum.request({\n chainId: chainInfo.id,\n method: 'particle_abi_encodeFunctionCall',\n params: [transactionInfo.data.to, 'erc20_approve', [spender, approveAmount]],\n });\n\n updateTransaction({\n data: encodeData,\n });\n }\n };\n\n return (\n <>\n <style>{styles as unknown as string}</style>\n {transactionInfo?.type ||\n renderPageType === RenderPageType.SIGN_TYPE_DATA ||\n renderPageType === RenderPageType.SIGN_MESSAGE ? (\n <div\n className={`info-sign info-sign-${transactionInfo?.type}`}\n data-type={transactionInfo?.type}\n ref={infoSignRef}\n >\n {!hasSetPaymentPassword && (\n <div className='has-payment-password' data-telegram={isTelegramWebApp()}>\n <div className='has-payment-password-icon'></div>\n <div className='has-payment-password-tip'>{t('account.waring_tip1')}</div>\n <div className='has-payment-password-set' onClick={setPaymentPassword}>\n {t('account.set')}\n </div>\n </div>\n )}\n <div className={'scroll-part' + (hasSetPaymentPassword ? '' : ' no-password-tip')}>\n <Menu userInfo={userInfo} transactionInfo={transactionInfo} />\n <div className='info-request'>\n {modalOptions.erc4337 && method !== EvmRpcMethod.ethSendTransaction && (\n // <div className=\"aa-tag\">AA</div>\n <div className='aa-icon'>\n <Image\n src={getAAIcon(\n ((modalOptions.erc4337 as any)?.name ?? 'BICONOMY').toLowerCase(),\n modalOptions.themeType || 'light'\n )}\n fallback={defaultTokenIcon}\n preview={false}\n />\n </div>\n )}\n {headerTitle}\n </div>\n <div className='info-title'>\n <Image src={getChainIcon(chainInfo)} fallback={defaultTokenIcon} preview={false} />\n {getChainDisplayName(chainInfo)}\n </div>\n <CopyToClipboard text={addressDisplayed || ''} onCopy={() => message.success(t('new.copied_to'))}>\n <div className='info-address'>\n {shortString(addressDisplayed)}\n <div className='copy-icon'>\n <IconCopy />\n </div>\n </div>\n </CopyToClipboard>\n <div className='info-des'>{headerDes}</div>\n <div className='apart-line'></div>\n {transactionInfo?.type === TransactionSmartType.NativeTransfer ||\n transactionInfo?.type === TransactionSmartType.ERC20_TRANSFER ||\n transactionInfo?.type === TransactionSmartType.ERC20_APPROVE ||\n transactionInfo?.type === TransactionSmartType.ERC721_TRANFER ||\n transactionInfo?.type === TransactionSmartType.ERC1155_TRANFER ||\n transactionInfo?.type === TransactionSmartType.SEAPORT_CANCEL_ORDER ||\n transactionInfo?.type === TransactionSmartType.SEAPORT_FULFILL_ORDER ||\n transactionInfo?.type === TransactionSmartType.SEAPORT_NFT_LISTING ? (\n <NewErcTransfers\n setDisplayDetail={setDisplayDetail}\n displayDetail={displayDetail}\n gasError={gasError}\n setGasVis={setGasVis}\n formatFunction={formatFunction}\n transactionInfo={transactionInfo}\n changeApproveAmount={changeApproveAmount}\n setChangeApproveAmount={editApproveAmount}\n signLoading={loading}\n signMessage={signMessage}\n chainInfo={chainInfo}\n signMethod={method}\n />\n ) : method === EvmRpcMethod.ethSendTransaction ? (\n signTransaction()\n ) : method !== EvmRpcMethod.ethSendTransaction ? (\n signMessage()\n ) : (\n ''\n )}\n </div>\n\n <div className='btn-box'>\n <div>\n <Button className='btn-cancel' onClick={cancelSign}>\n {t('common.cancel')}\n </Button>\n <Button\n className={`btn-approve ${gasError || hasSecurityRisk ? 'still-confirm' : ''}`}\n onClick={() => {\n if (hasSecurityRisk) {\n setRiskPrompt(true);\n } else {\n approveSign();\n }\n }}\n loading={loading}\n disabled={approveDisabled(transactionData)}\n >\n {isErc4361\n ? t('new.sign_in')\n : gasError || hasSecurityRisk\n ? t('common.still_confirm')\n : t('common.confirm')}\n </Button>\n </div>\n\n <PowerFooter></PowerFooter>\n </div>\n\n {hasSecurityRisk && transactionInfo?.securityDetection && (\n <>\n <RiskReminder securityDetection={transactionInfo?.securityDetection}></RiskReminder>\n <Modal\n className='risk-modal'\n open={riskPrompt}\n closeIcon={<CircleClose />}\n centered\n maskClosable={false}\n onCancel={() => {\n setRiskPrompt(false);\n }}\n getContainer={() => {\n return authCoreModal.rootBody as HTMLDivElement;\n }}\n >\n <style>{riskModalStyle as unknown as string}</style>\n <div className='risk-modal-title'>{t('sign.risk_hint_title')}</div>\n <RiskTypography\n className='risk-modal-content'\n securityDetection={transactionInfo?.securityDetection}\n title={t('sign.risk_identified') as string}\n />\n <div className='risk-bottom-btn-box'>\n <Button\n className='primary-antd-btn secondary'\n onClick={() => {\n setRiskPrompt(false);\n }}\n >\n {t('common.cancel')}\n </Button>\n <Button\n className='primary-antd-btn danger'\n onClick={() => {\n approveSign();\n setRiskPrompt(false);\n }}\n >\n {t('common.confirm')}\n </Button>\n </div>\n </Modal>\n </>\n )}\n\n <EvmGas openGasDrawer={() => setGasVis(false)} visible={gasVis} />\n </div>\n ) : (\n <ParticleLoading />\n )}\n </>\n );\n}\n\nexport default EvmSign;\n", "import { Button, Modal } from 'antd';\nimport React from 'react';\nimport { useAuthCoreModal } from '..';\nimport { useTranslation } from '../../context';\nimport { useEVMTransaction } from '../../pages/sign/evmContextProvider';\nimport { useAuthCore } from './useAuthCore';\nimport { useEthereum } from './useEthereum';\n\nconst usePending = () => {\n const { t } = useTranslation();\n const { transactionData, fetchPendingTransactions } = useEVMTransaction();\n\n const { address } = useEthereum();\n const { openWallet } = useAuthCore();\n const { authCoreModal } = useAuthCoreModal();\n\n const pendingModalRef = React.useRef<any>();\n\n const { authCoreModalClose } = useAuthCoreModal();\n\n const checkPending = async (approveSign: any, pendingConfirm: boolean) => {\n let pendingTransactions = [];\n\n if (\n transactionData &&\n transactionData.action !== 'cancel' &&\n transactionData.action !== 'speedup' &&\n !pendingConfirm\n ) {\n pendingTransactions = await fetchPendingTransactions(address as string);\n }\n\n if (\n transactionData &&\n transactionData.action !== 'cancel' &&\n transactionData.action !== 'speedup' &&\n !pendingConfirm &&\n (pendingTransactions?.length >= 3 ||\n !!pendingTransactions.find((item: any) => {\n const now = new Date().getTime();\n const timestamp = new Date(item.timestamp * 1000).getTime();\n return now - timestamp > 30 * 60 * 1000;\n }))\n ) {\n // \u5F53\u8BE5\u5730\u5740\u6709\u8D85\u8FC73\u7B14\u4EA4\u6613\u6B63\u5728Pending\u6216\u4E00\u7B14\u4EA4\u6613timestamp\u8D85\u8FC730\u5206\u949F\u672A\u5B8C\u6210\u65F6\uFF0C\u7528\u6237\u518D\u6B21\u786E\u8BA4\u4EA4\u6613\u65F6\u5C06\u5F39\u51FA\u6B64\u5F39\u7A97\n console.log('pendingTransactions', pendingTransactions);\n\n pendingModalRef.current = Modal.warning({\n className: 'pending-warning-modal auth-core-modal-warning',\n content: (\n <div className='content-wrap'>\n <div className='content'>{t('new.transaction_pending_v2')}</div>\n <div className='footer-btns'>\n <Button\n className='cancel-btn continue-btn'\n disabled={pendingTransactions?.length >= 10}\n type='default'\n onClick={() => {\n approveSign(true);\n pendingModalRef.current.destroy();\n }}\n >\n {t('new.continue')}\n </Button>\n <Button\n className='process-now-btn'\n type='primary'\n onClick={() => {\n // \u53D6\u6D88\u7B7E\u540D\uFF0C\u8DF3\u8F6C\u5230wallet\n console.log('>>>', '\u53D6\u6D88\u7B7E\u540D\uFF0C\u8DF3\u8F6C\u5230wallet');\n\n pendingModalRef.current.destroy();\n authCoreModalClose();\n\n setTimeout(() => {\n openWallet({\n pathName: '/tokenDetail.html',\n windowSize: 'small',\n query: {\n tokenAddress: 'native',\n pageMode: 'Simple',\n },\n topMenuType: 'close',\n });\n // \u5982\u679C\u4E0D\u8BBE\u7F6E\u5EF6\u8FDF\uFF0C\u4F1A\u51FA\u73B0\u6253\u5F00\u9A6C\u4E0A\u88AB\u5173\u95ED\u7684\u60C5\u51B5 wwl\n }, 300);\n }}\n >\n {t('new.process_now')}\n </Button>\n </div>\n </div>\n ),\n closable: true,\n maskClosable: false,\n getContainer: () => {\n return (authCoreModal.rootBody as HTMLElement).querySelector('.info-sign') as HTMLElement;\n },\n });\n return true;\n }\n\n return false;\n };\n\n return {\n checkPending,\n };\n};\n\nexport default usePending;\n", "import { intToHex } from '@ethereumjs/util';\nimport {\n EvmEnhancedMethod,\n EvmRpcMethod,\n GasFeeMode,\n getEVMChainInfoById,\n type EVMTransaction,\n type GasFee,\n type GasFeeResult,\n type TokenPrice,\n type TotalAmount,\n type TransactionData,\n type TxData,\n} from '@particle-network/auth-core';\nimport { useSetState } from 'ahooks';\nimport React, { createContext, useCallback, useContext, useEffect, useState } from 'react';\nimport type { Chain as ViemChain } from 'viem/chains';\nimport { mainnet } from 'viem/chains';\nimport { useModalOptions } from '../../context';\nimport { useEthereum } from '../../context/hooks';\nimport useMessage from '../../context/hooks/useMessage';\nimport { bnToHex } from '../../utils/common-utils';\nimport { ethereumUtils } from '../../utils/ethereumUtils';\nimport { toWei } from '../../utils/number-utils';\nimport { isEIP1559Type } from '../../utils/transaction-utils';\n\ninterface EVMState {\n setTransaction: (tx: EVMTransaction) => void;\n transactionData?: EVMTransaction;\n updateTransaction: (tx: Partial<TransactionData>) => void;\n gasFeeMode?: GasFeeMode;\n setGasFeeMode: (mode: GasFeeMode) => void;\n fetchGasAsync: (params: { addresses: string[]; from: string; to?: string; value?: string; data?: string }) => void;\n gasFeeDisplay?: GasFee;\n totalAmountDisplay?: TotalAmount;\n tokenPrice?: TokenPrice[];\n gasError?: { code: number; message: string };\n gasFeeResult?: GasFeeResult;\n latestGasLimit?: string;\n currentChain: ViemChain;\n fetchPendingTransactions: (account: string) => Promise<any[]>;\n}\n\nconst defaultData: EVMState = {\n setTransaction: () => {},\n updateTransaction: () => {},\n setGasFeeMode: () => {},\n fetchGasAsync: () => {},\n currentChain: mainnet,\n fetchPendingTransactions: () => Promise.resolve([]),\n};\n\nexport const EVMContext = createContext<EVMState>(defaultData);\n\nexport const EVMContextProvider = (props: { method: string; param: unknown; children: React.ReactNode }) => {\n const { modalOptions } = useModalOptions();\n const [evmTransaction, setEVMTransaction] = useSetState<EVMTransaction>({ from: '' });\n const [latestGasLimit, setLatestGasLimit] = useState<string>();\n const [tokenPrice, setTokenProce] = useState<TokenPrice[]>();\n const [feeMode, setFeeMode] = useState<GasFeeMode>(GasFeeMode.custom);\n const [gasError, setGasError] = useState<{ code: number; message: string }>();\n const [gasFeeResult, setGasFeeResult] = useState<GasFeeResult>();\n const [gasFeeDisplay, setGasFeeDisplay] = useState<GasFee>();\n const [totalAmountDisplay, setTotalAmountDisplay] = useState<TotalAmount>();\n const message = useMessage();\n const { chainInfo } = useEthereum();\n\n const getTxChain = () => {\n if (props.method === EvmRpcMethod.ethSendTransaction && (props.param as TxData).chainId) {\n const chainId = (props.param as TxData).chainId;\n const chain = getEVMChainInfoById(Number(chainId));\n return chain ?? chainInfo;\n } else {\n return chainInfo;\n }\n };\n //\u4EA4\u6613\u8FC7\u7A0B\u4E2D\u5982\u679C\u5207\u6362\u4E86\u7F51\u7EDC\uFF0C\u4E0D\u5F71\u54CD\u5F53\u524D\u4EA4\u6613\n const [currentChain] = useState<ViemChain>(getTxChain());\n\n const updateTransaction = (data: Partial<TransactionData>) => {\n if (evmTransaction) {\n if (data.maxFeePerGas) {\n setEVMTransaction({ maxFeePerGas: data.maxFeePerGas });\n }\n\n if (data.maxPriorityFeePerGas) {\n setEVMTransaction({ maxPriorityFeePerGas: data.maxPriorityFeePerGas });\n }\n\n if (data.gasLimit) {\n setEVMTransaction({ gasLimit: data.gasLimit });\n }\n\n if (data.gasPrice) {\n setEVMTransaction({ gasPrice: data.gasPrice });\n }\n\n if (data.data) {\n setEVMTransaction({ data: data.data });\n }\n }\n };\n\n useEffect(() => {\n if (evmTransaction && gasFeeResult) {\n const params = {\n gasLimit: evmTransaction.gasLimit || '0x0',\n baseFee: bnToHex(toWei(gasFeeResult.baseFee, 'gwei')),\n maxFeePerGas: evmTransaction.maxFeePerGas,\n maxPriorityFeePerGas: evmTransaction.maxPriorityFeePerGas,\n gasPrice: evmTransaction.gasPrice,\n };\n const gasFeeDisplay = ethereumUtils.gasFee(params);\n setGasFeeDisplay(gasFeeDisplay);\n\n const totalAmountDisplay = ethereumUtils.totalAmount({\n value: evmTransaction.value,\n gasLimit: evmTransaction.gasLimit || '0x0',\n baseFee: bnToHex(toWei(gasFeeResult.baseFee, 'gwei')),\n maxFeePerGas: evmTransaction.maxFeePerGas,\n maxPriorityFeePerGas: evmTransaction.maxPriorityFeePerGas,\n gasPrice: evmTransaction.gasPrice,\n });\n setTotalAmountDisplay(totalAmountDisplay);\n }\n }, [evmTransaction, gasFeeResult]);\n\n const setTransaction = (transaction: EVMTransaction) => {\n setEVMTransaction(transaction);\n if (transaction.gasLevel) {\n setFeeMode(transaction.gasLevel as GasFeeMode);\n console.log('update gas fee mode', transaction.gasLevel);\n } else {\n setFeeMode(GasFeeMode.custom);\n console.log('update gas fee mode (default)', GasFeeMode.custom);\n }\n };\n\n const setGasFeeMode = (gasFeeMode: GasFeeMode) => {\n setFeeMode(gasFeeMode);\n console.log('update gas fee mode (setGasFeeMode)', gasFeeMode);\n if (evmTransaction && gasFeeResult && gasFeeMode !== GasFeeMode.custom) {\n if (isEIP1559Type(evmTransaction.type)) {\n setEVMTransaction({\n maxFeePerGas: bnToHex(toWei(gasFeeResult[gasFeeMode].maxFeePerGas, 'gwei')),\n maxPriorityFeePerGas: bnToHex(toWei(gasFeeResult[gasFeeMode].maxPriorityFeePerGas, 'gwei')),\n });\n } else {\n setEVMTransaction({\n gasPrice: bnToHex(toWei(gasFeeResult[gasFeeMode].maxFeePerGas, 'gwei')),\n });\n }\n }\n };\n\n const fetchGasAsync = (params: { addresses: string[]; from: string; to?: string; value?: string; data?: string }) => {\n fetchGas(params)\n .then(({ gasFee, prices, gasLimit }) => {\n setGasFeeResult(gasFee);\n setTokenProce(prices);\n let gasLimitValue: string;\n if (typeof gasLimit === 'string') {\n gasLimitValue = gasLimit;\n setLatestGasLimit(gasLimit);\n setGasError(undefined);\n } else {\n if (!latestGasLimit) {\n gasLimitValue = intToHex(50000);\n setLatestGasLimit(gasLimitValue);\n setGasError({\n message: gasLimit.message,\n code: -32000,\n });\n } else {\n gasLimitValue = latestGasLimit;\n }\n }\n\n if (evmTransaction) {\n if (isEIP1559Type(evmTransaction.type)) {\n //eip 1559\n if (feeMode && feeMode !== GasFeeMode.custom) {\n evmTransaction.maxFeePerGas = bnToHex(toWei(gasFee[feeMode].maxFeePerGas, 'gwei'));\n evmTransaction.maxPriorityFeePerGas = bnToHex(toWei(gasFee[feeMode].maxPriorityFeePerGas, 'gwei'));\n } else if (!evmTransaction.maxFeePerGas || !evmTransaction.maxPriorityFeePerGas) {\n evmTransaction.maxFeePerGas = bnToHex(toWei(gasFee.medium.maxFeePerGas, 'gwei'));\n evmTransaction.maxPriorityFeePerGas = bnToHex(toWei(gasFee.medium.maxPriorityFeePerGas, 'gwei'));\n if (!evmTransaction.gasLimit) {\n setFeeMode(GasFeeMode.medium);\n console.log('update gas fee mode (fulfilled)', GasFeeMode.medium);\n }\n }\n } else {\n if (feeMode && feeMode !== GasFeeMode.custom) {\n evmTransaction.gasPrice = bnToHex(toWei(gasFee[feeMode].maxFeePerGas, 'gwei'));\n } else if (!evmTransaction.gasPrice) {\n evmTransaction.gasPrice = bnToHex(toWei(gasFee.medium.maxFeePerGas, 'gwei'));\n if (!evmTransaction.gasLimit) {\n setFeeMode(GasFeeMode.medium);\n console.log('update gas fee mode (fulfilled)', GasFeeMode.medium);\n }\n }\n }\n\n if (\n !evmTransaction.gasLimit ||\n feeMode !== GasFeeMode.custom ||\n Number(evmTransaction.gasLimit) < Number(gasLimitValue)\n ) {\n evmTransaction.gasLimit = gasLimitValue;\n