Skip to content

Commit

Permalink
feat: make mask as first payment token (#8947)
Browse files Browse the repository at this point in the history
* feat: mask as the first priority payment token

* fix: typo
  • Loading branch information
nuanyang233 committed Mar 6, 2023
1 parent cad58c9 commit bc335a1
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 95 deletions.
Expand Up @@ -24,20 +24,19 @@ import {
useChainContext,
useChainIdSupport,
useFungibleToken,
useFungibleTokenBalance,
useFungibleTokenPrice,
useGasOptions,
useMaskTokenAddress,
useNativeToken,
useNativeTokenPrice,
useReverseAddress,
useWallet,
useWeb3State,
} from '@masknet/web3-hooks-base'
import {
formatBalance,
formatCurrency,
isGreaterThan,
isLessThan,
leftShift,
pow10,
toFixed,
Expand Down Expand Up @@ -159,13 +158,11 @@ const ContractInteraction = memo(() => {
const { classes } = useStyles()
const location = useLocation()
const navigate = useNavigate()
const wallet = useWallet()

const { smartPayChainId } = useContainer(PopupContext)
const { Others, TransactionFormatter, Connection } = useWeb3State(NetworkPluginID.PLUGIN_EVM)
const { chainId, networkType } = useChainContext<NetworkPluginID.PLUGIN_EVM>()
const [transferError, setTransferError] = useState(false)
const [gasCurrency, setGasCurrency] = useState<string>()
const { value: request, loading: requestLoading, error } = useUnconfirmedRequest()
const { value: transactionDescription } = useAsync(async () => {
if (!request?.transactionContext?.chainId) return
Expand Down Expand Up @@ -251,72 +248,127 @@ const ContractInteraction = memo(() => {
return {}
}, [gasPrice, maxPriorityFeePerGas, maxFeePerGas, networkType, chainId, gasOptions, isSupport1559])

const handleChangeGasCurrency = useCallback(
async (address: string) => {
try {
if (!request) return
const { signableConfig } = PayloadEditor.fromPayload(request?.payload, {
chainId: request.owner ? smartPayChainId : chainId,
})

if (!signableConfig) return
const connection = Connection?.getConnection?.()

const gas = await connection?.estimateTransaction?.(signableConfig, undefined, {
paymentToken: address,
})

if (!gas) return

const config = request.payload.params!.map((param) =>
param === 'latest'
? param
: {
...param,
gas: toHex(gas),
},
)

await WalletRPC.updateUnconfirmedRequest({
...request.payload,
owner: request.owner,
identifier: request.identifier?.toText(),
paymentToken: address,
params: config,
})
} finally {
setGasCurrency(address)
}
},
[request, smartPayChainId, chainId],
)

// #region Smart Pay Gas currency
const [currencyMenu, openCurrencyMenu] = useGasCurrencyMenu(
NetworkPluginID.PLUGIN_EVM,
handleChangeGasCurrency,
gasCurrency,
StyledRadio,
)

// # region gas settings
const maskAddress = useMaskTokenAddress()
const { value: maskToken } = useFungibleToken(NetworkPluginID.PLUGIN_EVM, maskAddress)

const { PAYMASTER_MASK_CONTRACT_ADDRESS } = useSmartPayConstants(chainId)

const { value: allowance = '0' } = useERC20TokenAllowance(maskAddress, PAYMASTER_MASK_CONTRACT_ADDRESS, {
chainId: smartPayChainId,
})

const availableBalanceTooLow = isLessThan(formatBalance(allowance, maskToken?.decimals), 0.1)

const { value: currencyRatio } = useAsync(async () => {
if (!smartPayChainId) return
const depositPaymaster = new DepositPaymaster(smartPayChainId)
const ratio = await depositPaymaster.getRatio()

return ratio
}, [smartPayChainId])

const gasPriceEIP1559 = useMemo(
() => new BigNumber(maxFeePerGas ?? defaultPrices?.maxFeePerGas ?? 0, 16),
[maxFeePerGas, defaultPrices?.maxFeePerGas],
)

const gasPricePriorEIP1559 = useMemo(
() => new BigNumber(gasPrice ?? defaultPrices?.gasPrice ?? 0),
[gasPrice, defaultPrices?.gasPrice],
)

const { value: maskGasFee = ZERO } = useAsync(async () => {
if (!request || !currencyRatio || chainId !== smartPayChainId) return ZERO
const { signableConfig } = PayloadEditor.fromPayload(request.payload, {
chainId: request.owner ? smartPayChainId : chainId,
})

if (!signableConfig) return ZERO
const connection = Connection?.getConnection?.()
const gas = await connection?.estimateTransaction?.(signableConfig, undefined, {
paymentToken: maskToken?.address,
})

return new BigNumber(
toFixed(
(isSupport1559 ? gasPriceEIP1559 : gasPricePriorEIP1559)
.multipliedBy(gas ?? 0)
.integerValue()
.multipliedBy(currencyRatio),
0,
),
)
}, [
maskToken?.address,
request,
smartPayChainId,
chainId,
currencyRatio,
gasPriceEIP1559,
gasPricePriorEIP1559,
isSupport1559,
])

const { value: maskBalance = '0' } = useFungibleTokenBalance(undefined, maskToken?.address)

const { value: maskAllowance = '0' } = useERC20TokenAllowance(maskAddress, PAYMASTER_MASK_CONTRACT_ADDRESS, {
chainId: smartPayChainId,
})

const availableBalanceTooLow = useMemo(() => {
return isGreaterThan(maskGasFee, maskAllowance) || isGreaterThan(maskGasFee, maskBalance)
}, [maskAllowance, maskToken?.decimals, maskGasFee, maskBalance])

const handleChangeGasCurrency = useCallback(
async (address: string) => {
if (!request) return
const { signableConfig } = PayloadEditor.fromPayload(request?.payload, {
chainId: request.owner ? smartPayChainId : chainId,
})

if (!signableConfig) return
const connection = Connection?.getConnection?.()

const gas = await connection?.estimateTransaction?.(signableConfig, undefined, {
paymentToken: address,
})

if (!gas) return

const config = request.payload.params!.map((param) =>
param === 'latest'
? param
: {
...param,
gas: toHex(gas),
},
)

await WalletRPC.updateUnconfirmedRequest({
...request.payload,
owner: request.owner,
identifier: request.identifier?.toText(),
paymentToken: address,
params: config,
})
},
[request, smartPayChainId, chainId],
)

const [currencyMenu, openCurrencyMenu] = useGasCurrencyMenu(
NetworkPluginID.PLUGIN_EVM,
handleChangeGasCurrency,
request?.paymentToken,
StyledRadio,
)
useUpdateEffect(() => {
if (!request && !requestLoading && !error) {
navigate(PopupRoutes.Wallet, { replace: true })
}
}, [request, requestLoading, error])

// when the request's payment token is undefined and the mask balance is sufficient, the default is to use the mask as the payment token
useAsync(async () => {
if (smartPayChainId !== chainId || !maskToken?.address) return
if (!request?.paymentToken && !availableBalanceTooLow) {
await handleChangeGasCurrency(maskToken.address)
}
}, [availableBalanceTooLow, smartPayChainId, chainId, handleChangeGasCurrency, maskToken?.address])
// #endregion

// handlers
Expand All @@ -327,13 +379,13 @@ const ContractInteraction = memo(() => {
chainId: request.owner ? smartPayChainId : chainId,
owner: request.owner,
identifier: request.identifier?.toText(),
paymentToken: gasCurrency,
paymentToken: request.paymentToken,
})
navigate(-1)
} catch (error_) {
setTransferError(true)
}
}, [request, location.search, history, chainId, smartPayChainId, gasCurrency])
}, [request, location.search, history, chainId, smartPayChainId])

const [{ loading: rejectLoading }, handleReject] = useAsyncFn(async () => {
if (!request) return
Expand All @@ -342,25 +394,6 @@ const ContractInteraction = memo(() => {
}, [request])

// Wei
const gasPriceEIP1559 = new BigNumber(maxFeePerGas ?? defaultPrices?.maxFeePerGas ?? 0, 16)
const gasPricePriorEIP1559 = new BigNumber(gasPrice ?? defaultPrices?.gasPrice ?? 0)

const gasFee = useMemo(() => {
if (!gas) return ZERO
const result = (isSupport1559 ? gasPriceEIP1559 : gasPricePriorEIP1559).multipliedBy(gas ?? 0).integerValue()

if (!gasCurrency || isNativeTokenAddress(gasCurrency)) return result
if (!currencyRatio) return ZERO
return new BigNumber(toFixed(result.multipliedBy(currencyRatio), 0))
}, [
gas,
isSupport1559,
gasPriceEIP1559,
gasPricePriorEIP1559,
Others?.isNativeTokenAddress,
gasCurrency,
currencyRatio,
])

// token decimals
const tokenAmount = (amount ?? 0) as number
Expand All @@ -381,28 +414,36 @@ const ContractInteraction = memo(() => {
.times((!isNativeTokenInteraction ? tokenPrice : nativeTokenPrice) ?? 0)
.toString()

const gasFee = useMemo(() => {
if (!gas) return ZERO
const result = (isSupport1559 ? gasPriceEIP1559 : gasPricePriorEIP1559).multipliedBy(gas ?? 0).integerValue()

if (!request?.paymentToken || isNativeTokenAddress(request.paymentToken)) return result
if (!currencyRatio) return ZERO
return new BigNumber(toFixed(result.multipliedBy(currencyRatio), 0))
}, [
gas,
isSupport1559,
gasPriceEIP1559,
gasPricePriorEIP1559,
Others?.isNativeTokenAddress,
request?.paymentToken,
currencyRatio,
])

const gasFeeUSD = useMemo(() => {
if (!gasFee || gasFee.isZero()) return '0'
if (!gasCurrency || isNativeTokenAddress(gasCurrency)) {
if (!request?.paymentToken || isNativeTokenAddress(request.paymentToken)) {
return formatWeiToEther(gasFee).times(nativeTokenPrice ?? 0)
}

if (!maskToken || !maskTokenPrice) return '0'

return new BigNumber(formatBalance(gasFee, maskToken.decimals)).times(maskTokenPrice)
}, [gasFee, nativeTokenPrice, maskTokenPrice])
}, [gasFee, nativeTokenPrice, maskTokenPrice, request?.paymentToken])

const totalUSD = new BigNumber(gasFeeUSD).plus(tokenValueUSD).toString()

useUpdateEffect(() => {
if (!request && !requestLoading && !error) {
navigate(PopupRoutes.Wallet, { replace: true })
}
if (request?.paymentToken) {
setGasCurrency(request.paymentToken)
}
}, [request, requestLoading, error])

useTitle(typeName ?? t('popups_wallet_contract_interaction'))
const { value: domain } = useReverseAddress(NetworkPluginID.PLUGIN_EVM, to)

Expand Down Expand Up @@ -464,13 +505,13 @@ const ContractInteraction = memo(() => {
<FormattedBalance
value={gasFee}
decimals={
gasCurrency && !Others?.isNativeTokenAddress(gasCurrency)
request?.paymentToken && !Others?.isNativeTokenAddress(request?.paymentToken)
? maskToken?.decimals
: nativeToken?.decimals
}
significant={4}
symbol={
gasCurrency && !Others?.isNativeTokenAddress(gasCurrency)
request?.paymentToken && !Others?.isNativeTokenAddress(request?.paymentToken)
? maskToken?.symbol
: nativeToken?.symbol
}
Expand Down
Expand Up @@ -23,7 +23,7 @@ export const useUnconfirmedRequest = () => {
return {
owner: payload.owner,
identifier: payload.identifier ? ECKeyIdentifier.from(payload.identifier).unwrapOr(undefined) : undefined,
paymentToken: payload.paymentToken ?? nativeTokenAddress,
paymentToken: payload.paymentToken,
payload: omit(payload, 'owner', 'identifier', 'paymentToken') as JsonRpcPayload,
computedPayload,
formatterTransaction,
Expand Down
Expand Up @@ -75,7 +75,7 @@ export class SmartPayAccountAPI implements AbstractAccountAPI.Provider<NetworkPl
return
}

await userTransaction.assetUserOperation(this.web3.getWeb3(chainId), await getOverrides())
await userTransaction.assertUserOperation(this.web3.getWeb3(chainId), await getOverrides())
return this.bundler.sendUserOperation(chainId, await userTransaction.signUserOperation(signer))
}

Expand Down
2 changes: 1 addition & 1 deletion packages/web3-shared/evm/src/libs/UserTransaction.ts
Expand Up @@ -169,7 +169,7 @@ export class UserTransaction {
return contract
}

async assetUserOperation(web3: Web3, overrides?: Required<Pick<UserOperation, 'initCode' | 'nonce'>>) {
async assertUserOperation(web3: Web3, overrides?: Required<Pick<UserOperation, 'initCode' | 'nonce'>>) {
// from overrides
if (overrides) {
this.userOperation.nonce = overrides.nonce
Expand Down

0 comments on commit bc335a1

Please sign in to comment.