From 80f6d833830346523a841a11b26511aceb762dc2 Mon Sep 17 00:00:00 2001 From: Hancheng Zhou Date: Tue, 12 Jul 2022 13:12:09 +0800 Subject: [PATCH 1/2] feat(lucky drop): pre gas minus (#6467) * feat(lucky drop): pre gas minus * chore: reply code review * chore: solve error * fix: solve conflict * chore: format balance * chore: apply confition apply only sum of gas and total greater than balance * chore: increase estimate gas * chore: format significant * chore: reply code review * chore: format significant * chore: i18n * chore: reply code review * chore: use big number * chore: add useTransactionValue hook --- .../RedPacket/SNSAdaptor/RedPacket/index.tsx | 6 +- .../SNSAdaptor/RedPacketConfirmDialog.tsx | 88 ++++++++++++++----- .../SNSAdaptor/hooks/useCreateCallback.tsx | 17 +++- .../plugins/RedPacket/SNSAdaptor/index.tsx | 24 ++++- .../src/plugins/RedPacket/locales/en-US.json | 3 + packages/plugin-infra/src/web3/EVM/index.ts | 1 + .../src/web3/EVM/useTransactionValue.ts | 27 ++++++ .../descriptors/RedPacket.ts | 7 +- 8 files changed, 140 insertions(+), 33 deletions(-) create mode 100644 packages/plugin-infra/src/web3/EVM/useTransactionValue.ts diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacket/index.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacket/index.tsx index a5506723eb41..9dd91b337411 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacket/index.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacket/index.tsx @@ -109,7 +109,7 @@ export function RedPacket(props: RedPacketProps) { return t.description_claimed( availability.claimed_amount ? { - amount: formatBalance(availability.claimed_amount, token.decimals, 8), + amount: formatBalance(availability.claimed_amount, token.decimals, 2), symbol: token.symbol, } : { amount: '-', symbol: '-' }, @@ -122,7 +122,7 @@ export function RedPacket(props: RedPacketProps) { if (listOfStatus.includes(RedPacketStatus.expired) && canRefund) return t.description_refund({ - balance: formatBalance(availability.balance, token.decimals), + balance: formatBalance(availability.balance, token.decimals, 2), symbol: token.symbol ?? '-', }) if (listOfStatus.includes(RedPacketStatus.refunded)) return t.description_refunded() @@ -130,7 +130,7 @@ export function RedPacket(props: RedPacketProps) { if (listOfStatus.includes(RedPacketStatus.empty)) return t.description_empty() if (!payload.password) return t.description_broken() return t.description_failover({ - total: formatBalance(payload.total, token.decimals), + total: formatBalance(payload.total, token.decimals, 2), symbol: token.symbol ?? '-', shares: payload.shares.toString() ?? '-', }) diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketConfirmDialog.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketConfirmDialog.tsx index d3d9353d8e76..e7feaca80ae2 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketConfirmDialog.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketConfirmDialog.tsx @@ -1,19 +1,28 @@ import BigNumber from 'bignumber.js' import classNames from 'classnames' import { useCallback, useEffect, useMemo, useRef } from 'react' +import { + useAccount, + useChainId, + useBalance, + useNetworkType, + useWeb3, + useNativeToken, + useNativeTokenAddress, +} from '@masknet/plugin-infra/web3' import { chainResolver, explorerResolver, isNativeTokenAddress, useRedPacketConstants } from '@masknet/web3-shared-evm' import { Grid, Link, Paper, Typography } from '@mui/material' import { makeStyles } from '@masknet/theme' import LaunchIcon from '@mui/icons-material/Launch' import { FormattedBalance, useOpenShareTxDialog } from '@masknet/shared' +import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' import { useI18N } from '../locales' -import { RedPacketSettings, useCreateCallback } from './hooks/useCreateCallback' -import { useAccount, useChainId, useNetworkType, useWeb3 } from '@masknet/plugin-infra/web3' -import { NetworkPluginID, formatBalance } from '@masknet/web3-shared-base' +import { RedPacketSettings, useCreateCallback, useCreateParams } from './hooks/useCreateCallback' +import { useTransactionValue } from '@masknet/plugin-infra/web3-evm' +import { NetworkPluginID, formatBalance, isSameAddress } from '@masknet/web3-shared-base' import type { RedPacketJSONPayload, RedPacketRecord } from '../types' import { RedPacketRPC } from '../messages' import { PluginWalletStatusBar } from '../../../utils' -import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' import { ChainBoundary } from '../../../web3/UI/ChainBoundary' const useStyles = makeStyles()((theme) => ({ @@ -81,6 +90,7 @@ export function RedPacketConfirmDialog(props: ConfirmRedPacketFormProps) { const t = useI18N() const { onBack, settings, onCreated, onClose } = props const { classes } = useStyles() + const { value: balance = '0', loading: loadingBalance } = useBalance(NetworkPluginID.PLUGIN_EVM) const chainId = useChainId(NetworkPluginID.PLUGIN_EVM) useEffect(() => { if (settings?.token?.chainId !== chainId) onClose() @@ -93,11 +103,27 @@ export function RedPacketConfirmDialog(props: ConfirmRedPacketFormProps) { const web3 = useWeb3(NetworkPluginID.PLUGIN_EVM) const account = useAccount(NetworkPluginID.PLUGIN_EVM) const networkType = useNetworkType(NetworkPluginID.PLUGIN_EVM) + const nativeTokenAddress = useNativeTokenAddress(NetworkPluginID.PLUGIN_EVM) const { address: publicKey, privateKey } = useMemo( () => web3?.eth.accounts.create() ?? { address: '', privateKey: '' }, [web3], )! - const [{ loading: isCreating }, createCallback] = useCreateCallback(settings!, contract_version, publicKey) + const { value: nativeToken } = useNativeToken(NetworkPluginID.PLUGIN_EVM) + + // #region amount minus estimate gas fee + const { value: createParams } = useCreateParams(settings!, contract_version, publicKey) + const isNativeToken = isSameAddress(settings?.token?.address, nativeTokenAddress) + const { transactionValue, estimateGasFee } = useTransactionValue(settings?.total, createParams?.gas) + const isWaitGasBeMinus = (!estimateGasFee || loadingBalance) && isNativeToken + const isBalanceInsufficient = new BigNumber(transactionValue).isLessThanOrEqualTo(0) + const total = isNativeToken ? (isBalanceInsufficient ? '0' : transactionValue) : (settings?.total as string) + const formatTotal = formatBalance(total, settings?.token?.decimals ?? 18, isNativeToken ? 3 : 0) + const [{ loading: isCreating }, createCallback] = useCreateCallback( + { ...settings!, total }, + contract_version, + publicKey, + ) + // #endregion const openShareTxDialog = useOpenShareTxDialog() const createRedpacket = useCallback(async () => { const result = await createCallback() @@ -216,26 +242,45 @@ export function RedPacketConfirmDialog(props: ConfirmRedPacketFormProps) { - {settings?.isRandom ? null : ( + {!estimateGasFee ? null : ( <> - {t.amount_per_share()} + {t.estimate_gas_fee()} )} + {settings?.isRandom ? null : ( + <> + + + {t.amount_per_share()} + + + + + {isBalanceInsufficient + ? '0' + : new BigNumber(formatTotal).div(settings?.shares ?? 1).toFixed(6)}{' '} + {settings?.token?.symbol} + + + + )} + {t.total_amount()} @@ -243,12 +288,7 @@ export function RedPacketConfirmDialog(props: ConfirmRedPacketFormProps) { - + {formatTotal} {settings?.token?.symbol} @@ -261,11 +301,17 @@ export function RedPacketConfirmDialog(props: ConfirmRedPacketFormProps) { - - {t.send_symbol({ - amount: formatBalance(settings?.total, settings?.token?.decimals ?? 0), - symbol: settings?.token?.symbol ?? '-', - })} + + {!isBalanceInsufficient + ? t.send_symbol({ + amount: formatTotal, + symbol: settings?.token?.symbol ?? '-', + }) + : t.insufficient_balance()} diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/hooks/useCreateCallback.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/hooks/useCreateCallback.tsx index dd57a2476f68..9fef50607064 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/hooks/useCreateCallback.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/hooks/useCreateCallback.tsx @@ -1,5 +1,5 @@ import { useCallback } from 'react' -import { useAsyncFn } from 'react-use' +import { useAsync, useAsyncFn } from 'react-use' import Web3Utils from 'web3-utils' import { omit } from 'lodash-unified' import { useAccount, useChainId, useWeb3Connection, useWeb3 } from '@masknet/plugin-infra/web3' @@ -58,7 +58,11 @@ interface CreateParams { gasError: Error | null } -export function useCreateParams(redPacketSettings: RedPacketSettings | undefined, version: number, publicKey: string) { +export function useCreateParamsCallback( + redPacketSettings: RedPacketSettings | undefined, + version: number, + publicKey: string, +) { const account = useAccount(NetworkPluginID.PLUGIN_EVM) const chainId = useChainId(NetworkPluginID.PLUGIN_EVM) const { NATIVE_TOKEN_ADDRESS } = useTokenConstants(chainId) @@ -111,11 +115,16 @@ export function useCreateParams(redPacketSettings: RedPacketSettings | undefined return getCreateParams } +export function useCreateParams(redPacketSettings: RedPacketSettings, version: number, publicKey: string) { + const getCreateParams = useCreateParamsCallback(redPacketSettings, version, publicKey) + return useAsync(() => getCreateParams(), [redPacketSettings, version, publicKey]) +} + export function useCreateCallback(redPacketSettings: RedPacketSettings, version: number, publicKey: string) { const account = useAccount(NetworkPluginID.PLUGIN_EVM) const chainId = useChainId(NetworkPluginID.PLUGIN_EVM) const redPacketContract = useRedPacketContract(chainId, version) - const getCreateParams = useCreateParams(redPacketSettings, version, publicKey) + const getCreateParams = useCreateParamsCallback(redPacketSettings, version, publicKey) const connection = useWeb3Connection(NetworkPluginID.PLUGIN_EVM) const web3 = useWeb3(NetworkPluginID.PLUGIN_EVM) @@ -162,5 +171,5 @@ export function useCreateCallback(redPacketSettings: RedPacketSettings, version: } } return { hash, receipt } - }, [account, connection, redPacketContract, redPacketSettings, chainId, getCreateParams]) + }, [account, connection, redPacketContract, redPacketSettings, chainId]) } diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/index.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/index.tsx index b052fd414538..1b8aa23efa1d 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/index.tsx @@ -1,5 +1,12 @@ import { type Plugin, usePluginWrapper, PluginId } from '@masknet/plugin-infra/content-script' -import { ChainId, SchemaType, chainResolver, networkResolver, NetworkType } from '@masknet/web3-shared-evm' +import { + ChainId, + SchemaType, + chainResolver, + networkResolver, + NetworkType, + isNativeTokenAddress, +} from '@masknet/web3-shared-evm' import { base } from '../base' import { RedPacketMetaKey, RedPacketNftMetaKey } from '../constants' import { @@ -8,6 +15,7 @@ import { renderWithRedPacketMetadata, renderWithRedPacketNftMetadata, } from './helpers' +import { useI18N } from '../locales' import type { RedPacketJSONPayload, RedPacketNftJSONPayload } from '../types' import RedPacketDialog from './RedPacketDialog' import { RedPacketInPost } from './RedPacketInPost' @@ -147,6 +155,7 @@ interface ERC20RedpacketBadgeProps { function ERC20RedpacketBadge(props: ERC20RedpacketBadgeProps) { const { payload } = props + const t = useI18N() const { value: fetchedToken } = useFungibleToken( NetworkPluginID.PLUGIN_EVM, payload.token?.address ?? payload.token?.address, @@ -156,9 +165,16 @@ function ERC20RedpacketBadge(props: ERC20RedpacketBadgeProps) { const tokenDetailed = payload.token?.schema === SchemaType.Native ? nativeCurrency : payload.token ?? fetchedToken return (
- A Lucky Drop with{' '} - {formatBalance(payload.total, tokenDetailed?.decimals ?? 0)} $ - {tokenDetailed?.symbol ?? tokenDetailed?.name ?? 'Token'} from {payload.sender.name} + + {t.badge({ + balance: formatBalance( + payload.total, + tokenDetailed?.decimals ?? 0, + isNativeTokenAddress(payload.token?.address) ? 6 : 0, + ), + tokenName: tokenDetailed?.symbol ?? tokenDetailed?.name ?? 'Token', + sender: payload.sender.name, + })}
) } diff --git a/packages/mask/src/plugins/RedPacket/locales/en-US.json b/packages/mask/src/plugins/RedPacket/locales/en-US.json index fb7fb85fe4c0..8f79904442b5 100644 --- a/packages/mask/src/plugins/RedPacket/locales/en-US.json +++ b/packages/mask/src/plugins/RedPacket/locales/en-US.json @@ -2,6 +2,7 @@ "promote": "🧧🧧🧧 Try sending Lucky Drop to your friends with tokens or NFTs to share the joy now! Install Mask.io to send your first Lucky Drop.", "promote_short": "🧧🧧🧧 Try sending Lucky Drop to your friends with Mask.io.", "nft_shift_select_tip": "You can also use {{text}} to select multiple NFTs.", + "badge": "A Lucky Drop with {{balance}} {{tokenName}} from {{sender}}", "collections": "Collections", "select_a_token": "Select a Token", "search": "Search", @@ -53,10 +54,12 @@ "select_existing": "History", "create_new": "New", "send_symbol": "Send {{amount}} {{symbol}}", + "insufficient_balance": "Insufficient Balance", "back": "Back", "hint": "You can withdraw the remaining balance 24 hours after the Lucky Drop is sent.", "total_amount": "Total Amount", "amount_per_share": "Amount per Share", + "estimate_gas_fee": "Estimate gas fee", "shares": "Shares", "average": "Average", "random": "Random", diff --git a/packages/plugin-infra/src/web3/EVM/index.ts b/packages/plugin-infra/src/web3/EVM/index.ts index edb258a194ec..9076bf6a2163 100644 --- a/packages/plugin-infra/src/web3/EVM/index.ts +++ b/packages/plugin-infra/src/web3/EVM/index.ts @@ -8,6 +8,7 @@ export * from './useERC721ContractIsApproveForAll' export * from './useERC721ContractSetApproveForAllCallback' export * from './useERC721TokenTransferCallback' export * from './useGasConfig' +export * from './useTransactionValue' export * from './useMulticall' export * from './useNativeTokenTransferCallback' export * from './useNativeTokenWrapperCallback' diff --git a/packages/plugin-infra/src/web3/EVM/useTransactionValue.ts b/packages/plugin-infra/src/web3/EVM/useTransactionValue.ts new file mode 100644 index 000000000000..a3e53b2f9ec1 --- /dev/null +++ b/packages/plugin-infra/src/web3/EVM/useTransactionValue.ts @@ -0,0 +1,27 @@ +import { useBalance } from '../useBalance' +import BigNumber from 'bignumber.js' +import { useGasConfig } from './useGasConfig' +import { useChainId } from '../useChainId' +import { NetworkPluginID } from '@masknet/web3-shared-base' + +export function useTransactionValue(originalValue: BigNumber.Value | undefined, gas: number | undefined) { + const { value: balance = '0' } = useBalance(NetworkPluginID.PLUGIN_EVM) + const chainId = useChainId(NetworkPluginID.PLUGIN_EVM) + + // #region amount minus estimate gas fee + const { gasPrice } = useGasConfig(chainId) + + const estimateGasFee = !gas + ? undefined + : gasPrice && gasPrice !== '0' + ? new BigNumber(gasPrice).multipliedBy(gas * 1.5).toFixed() + : undefined + + const transactionValue = new BigNumber(balance).isLessThan( + new BigNumber(originalValue ?? '0').plus(new BigNumber(estimateGasFee ?? '0')), + ) + ? new BigNumber(originalValue ?? '0').minus(estimateGasFee ?? '0').toFixed() + : (originalValue as string) + + return { transactionValue, estimateGasFee } +} diff --git a/packages/plugins/EVM/src/state/TransactionFormatter/descriptors/RedPacket.ts b/packages/plugins/EVM/src/state/TransactionFormatter/descriptors/RedPacket.ts index 3023490d64fe..72a305dbf3ab 100644 --- a/packages/plugins/EVM/src/state/TransactionFormatter/descriptors/RedPacket.ts +++ b/packages/plugins/EVM/src/state/TransactionFormatter/descriptors/RedPacket.ts @@ -3,6 +3,7 @@ import { ChainId, getNftRedPacketConstants, getRedPacketConstants, + isNativeTokenAddress, TransactionParameter, } from '@masknet/web3-shared-evm' import type { TransactionDescriptor } from '../types' @@ -26,7 +27,11 @@ export class RedPacketDescriptor implements TransactionDescriptor { }) const token = await connection?.getFungibleToken(parameters?._token_addr ?? '') - const amount = formatBalance(parameters?._total_tokens, token?.decimals) + const amount = formatBalance( + parameters?._total_tokens, + token?.decimals, + isNativeTokenAddress(parameters?._token_addr) ? 6 : 0, + ) return { chainId: context.chainId, From 44822e66ed9dddcaae844ac197e7ef84951c7ebc Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Tue, 12 Jul 2022 17:48:57 +0800 Subject: [PATCH 2/2] refactor: unlock nft --- .../TransactionFormatter/descriptors/ERC20.ts | 1 + .../TransactionFormatter/descriptors/ERC721.ts | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/plugins/EVM/src/state/TransactionFormatter/descriptors/ERC20.ts b/packages/plugins/EVM/src/state/TransactionFormatter/descriptors/ERC20.ts index ab391b45b151..85cec4441ca7 100644 --- a/packages/plugins/EVM/src/state/TransactionFormatter/descriptors/ERC20.ts +++ b/packages/plugins/EVM/src/state/TransactionFormatter/descriptors/ERC20.ts @@ -14,6 +14,7 @@ export class ERC20Descriptor implements TransactionDescriptor { }) for (const method of context.methods) { const parameters = method.parameters + switch (method.name) { case 'approve': if (parameters?.spender === undefined || parameters?.value === undefined) break diff --git a/packages/plugins/EVM/src/state/TransactionFormatter/descriptors/ERC721.ts b/packages/plugins/EVM/src/state/TransactionFormatter/descriptors/ERC721.ts index bcbf4dea0356..0048b51af5a8 100644 --- a/packages/plugins/EVM/src/state/TransactionFormatter/descriptors/ERC721.ts +++ b/packages/plugins/EVM/src/state/TransactionFormatter/descriptors/ERC721.ts @@ -1,5 +1,6 @@ import type { TransactionContext } from '@masknet/web3-shared-base' import type { ChainId, TransactionParameter } from '@masknet/web3-shared-evm' +import { Web3StateSettings } from '../../../settings' import type { TransactionDescriptor } from '../types' export class ERC721Descriptor implements TransactionDescriptor { @@ -10,6 +11,20 @@ export class ERC721Descriptor implements TransactionDescriptor { const parameters = method.parameters switch (method.name) { + case 'approve': + if (parameters?.to === undefined || parameters?.tokenId === undefined) break + + const connection = await Web3StateSettings.value.Connection?.getConnection?.({ + chainId: context.chainId, + }) + const contract = await connection?.getNonFungibleTokenContract(context.to) + + return { + chainId: context.chainId, + title: `Unlock ${contract?.symbol ?? 'token'} contract`, + description: `Unlock ${contract?.symbol ?? 'token'} contract`, + successfulDescription: `${contract?.symbol ?? 'token'} is unlocked successfully.`, + } case 'setApprovalForAll': if (parameters?.operator === undefined || parameters?.approved === undefined) break return {