diff --git a/packages/app/features/send/confirm/screen.tsx b/packages/app/features/send/confirm/screen.tsx index ce5100098..29cae9ffb 100644 --- a/packages/app/features/send/confirm/screen.tsx +++ b/packages/app/features/send/confirm/screen.tsx @@ -13,6 +13,7 @@ import { type YStackProps, } from '@my/ui' import { baseMainnet } from '@my/wagmi' +import { useQueryClient } from '@tanstack/react-query' import { IconAccount } from 'app/components/icons' import { IconCoin } from 'app/components/icons/IconCoin' import { coins } from 'app/data/coins' @@ -21,6 +22,7 @@ import { useSendScreenParams } from 'app/routers/params' import { assert } from 'app/utils/assert' import { hexToBytea } from 'app/utils/hexToBytea' import { useSendAccount } from 'app/utils/send-accounts' +import { throwIf } from 'app/utils/throwIf' import { useProfileLookup } from 'app/utils/useProfileLookup' import { useGenerateTransferUserOp, @@ -36,7 +38,7 @@ import { import { useEffect, useState } from 'react' import { useRouter } from 'solito/router' import { isAddress, parseUnits, type Hex } from 'viem' -import { useBalance } from 'wagmi' +import { useBalance, useEstimateFeesPerGas } from 'wagmi' type ProfileProp = NonNullable['data']> @@ -64,6 +66,7 @@ export function SendConfirmScreen() { } export function SendConfirm({ profile }: { profile: ProfileProp }) { + const queryClient = useQueryClient() const { data: sendAccount } = useSendAccount() const webauthnCreds = sendAccount?.send_account_credentials @@ -99,7 +102,16 @@ export function SendConfirm({ profile }: { profile: ProfileProp }) { }) const { data: gasEstimate } = useUserOpGasEstimate({ userOp }) - const { mutateAsync: sendUserOp, isPending: isTransferPending } = useUserOpTransferMutation() + const { + data: feesPerGas, + isLoading: isFeesPerGasLoading, + error: feesPerGasError, + } = useEstimateFeesPerGas() + const { + mutateAsync: sendUserOp, + isPending: isTransferPending, + isError: isTransferError, + } = useUserOpTransferMutation() const [error, setError] = useState() const { @@ -113,13 +125,9 @@ export function SendConfirm({ profile }: { profile: ProfileProp }) { }) const [dataFirstFetch, setDataFirstFetch] = useState() - console.log('gasEstimate', gasEstimate) - console.log('userOp', userOp) - - // need balance to check if user has enough to send - const canSubmit = !nonceIsLoading && + !isFeesPerGasLoading && Number(queryParams.amount) > 0 && coins.some((coin) => coin.token === sendToken) && (balance?.value ?? BigInt(0) >= amount) && @@ -131,13 +139,23 @@ export function SendConfirm({ profile }: { profile: ProfileProp }) { assert(!!balance, 'Balance is not available') assert(nonceError === null, `Failed to get nonce: ${nonceError}`) assert(nonce !== undefined, 'Nonce is not available') + throwIf(feesPerGasError) + assert(!!feesPerGas, 'Fees per gas is not available') assert(balance.value >= amount, 'Insufficient balance') const sender = sendAccount?.address as `0x${string}` assert(isAddress(sender), 'No sender address') + const _userOp = { + ...userOp, + maxFeePerGas: feesPerGas.maxFeePerGas, + maxPriorityFeePerGas: feesPerGas.maxPriorityFeePerGas, + } + console.log('gasEstimate', gasEstimate) + console.log('feesPerGas', feesPerGas) + console.log('userOp', _userOp) const receipt = await sendUserOp({ - userOp, + userOp: _userOp, webauthnCreds, }) assert(receipt.success, 'Failed to send user op') @@ -145,6 +163,7 @@ export function SendConfirm({ profile }: { profile: ProfileProp }) { } catch (e) { console.error(e) setError(e) + await queryClient.invalidateQueries({ queryKey: [useAccountNonce.queryKey] }) } } @@ -308,7 +327,7 @@ export function SendConfirm({ profile }: { profile: ProfileProp }) { > {(() => { switch (true) { - case isTransferPending: + case isTransferPending && !isTransferError: return ( <> diff --git a/packages/app/utils/useUserOpTransferMutation.ts b/packages/app/utils/useUserOpTransferMutation.ts index fbdf403b2..04c0ef77a 100644 --- a/packages/app/utils/useUserOpTransferMutation.ts +++ b/packages/app/utils/useUserOpTransferMutation.ts @@ -20,8 +20,15 @@ import { assert } from './assert' import { byteaToBase64 } from './byteaToBase64' import { signUserOp } from './userop' -// default user op with preset gas values that work -// will probably need to move this to the database +/** + * default user op with preset gas values that work will probably need to move this to the database. + * Paymaster post-op gas limit could be set dynamically based on the status of the paymaster if the price cache is + * outdated, otherwise, a lower post op gas limit around only 50K is needed. In case of needing to update cached price, + * the post op uses around 75K gas. + * + * - [example no update price](https://www.tdly.co/shared/simulation/a0122fae-a88c-47cd-901c-02de87901b45) + * - [Failed due to OOG](https://www.tdly.co/shared/simulation/c259922c-8248-4b43-b340-6ebbfc69bcea) + */ export const defaultUserOp: Pick< UserOperation<'v0.7'>, | 'callGasLimit' @@ -38,7 +45,7 @@ export const defaultUserOp: Pick< maxFeePerGas: 10000000n, maxPriorityFeePerGas: 10000000n, paymasterVerificationGasLimit: 150000n, - paymasterPostOpGasLimit: 75000n, + paymasterPostOpGasLimit: 100000n, } export type UseUserOpTransferMutationArgs = { diff --git a/packages/contracts/script/UpdateTokenPaymasterConfig.s.sol b/packages/contracts/script/UpdateTokenPaymasterConfig.s.sol new file mode 100644 index 000000000..ff6888128 --- /dev/null +++ b/packages/contracts/script/UpdateTokenPaymasterConfig.s.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.20; + +import {Script, console2} from "forge-std/Script.sol"; +import {Helper} from "../src/Helper.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "../src/TokenPaymaster.sol"; + +contract UpdateTokenPaymasterConfigScript is Script, Helper { + uint256 private constant PRICE_DENOM = 1e26; + uint40 private constant BASE_FEE_DEFAULT = 5e4; // ยข5 + + function setUp() public { + this.labels(); + } + + function run() public { + address addr = vm.envAddress("PAYMASTER"); + require(addr != address(0), "PAYMASTER env variable not set"); + + TokenPaymaster paymaster = TokenPaymaster(payable(addr)); + ( + uint256 _priceMarkup, + uint128 _minEntryPointBalance, + uint48 _refundPostopCost, + uint48 _priceMaxAge, + uint40 _baseFee, + address _rewardsPool + ) = paymaster.tokenPaymasterConfig(); + uint256 priceMarkup = vm.envOr("PRICE_MARKUP", _priceMarkup); + uint128 minEntryPointBalance = uint128(vm.envOr("MIN_ENTRY_POINT_BALANCE", _minEntryPointBalance)); + uint48 refundPostopCost = uint48(vm.envOr("REFUND_POSTOP_COST", _refundPostopCost)); + uint48 priceMaxAge = uint48(vm.envOr("PRICE_MAX_AGE", _priceMaxAge)); + uint40 baseFee = uint40(vm.envOr("BASE_FEE", _baseFee)); + address rewardsPool = vm.envOr("REWARDS_POOL", _rewardsPool); + + require( + priceMaxAge != _priceMaxAge || refundPostopCost != _refundPostopCost + || minEntryPointBalance != _minEntryPointBalance || priceMarkup != _priceMarkup || baseFee != _baseFee + || rewardsPool != _rewardsPool, + "Configs are the same" + ); + require(priceMaxAge > 0, "PRICE_MAX_AGE env variable not set"); + require(refundPostopCost > 0, "REFUND_POSTOP_COST env variable not set"); + require(minEntryPointBalance > 0, "MIN_ENTRY_POINT_BALANCE env variable not set"); + require(priceMarkup > 0, "PRICE_MARKUP env variable not set"); + require(baseFee > 0, "BASE_FEE env variable not set"); + + vm.startBroadcast(); + TokenPaymasterConfig memory tpc = TokenPaymasterConfig({ + priceMaxAge: priceMaxAge, + refundPostopCost: refundPostopCost, + minEntryPointBalance: minEntryPointBalance, + priceMarkup: priceMarkup, + baseFee: baseFee, + rewardsPool: rewardsPool + }); + paymaster.setTokenPaymasterConfig(tpc); + vm.stopBroadcast(); + } +} diff --git a/tilt/infra.tiltfile b/tilt/infra.tiltfile index b7457cf95..e63fc995f 100644 --- a/tilt/infra.tiltfile +++ b/tilt/infra.tiltfile @@ -215,7 +215,7 @@ local_resource( -v ./apps/aabundler/etc:/app/etc/aabundler \ -e "DEBUG={bundler_debug}" \ -e "DEBUG_COLORS=true" \ - docker.io/0xbigboss/bundler:0.7.0 \ + 0xbigboss/bundler:0.7.1 \ --port 3030 \ --config /app/etc/aabundler/aabundler.config.json \ --mnemonic /app/keys/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \