From f582c65b7797920f1cd2cdb820f8d248ff35afad Mon Sep 17 00:00:00 2001 From: portuu3 <> Date: Wed, 29 Jul 2026 13:04:27 +0200 Subject: [PATCH 1/3] Fix error loading fee with an invalid rpc --- .../components/Jobs/Create/CryptoPayForm.tsx | 64 +++++++++++++------ .../client/src/providers/WagmiProvider.tsx | 31 ++++----- 2 files changed, 61 insertions(+), 34 deletions(-) diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx index f9fce014bb..70e216b97e 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx @@ -7,6 +7,7 @@ import { Box, Button, Checkbox, + CircularProgress, FormControl, FormControlLabel, Grid, @@ -17,14 +18,10 @@ import { } from '@mui/material'; import { Decimal } from 'decimal.js'; import { ethers } from 'ethers'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { Address } from 'viem'; -import { - useAccount, - useReadContract, - useWalletClient, - usePublicClient, -} from 'wagmi'; +import { readContract } from 'viem/actions'; +import { useAccount, useWalletClient, usePublicClient } from 'wagmi'; import { TokenSelect } from '../../../components/TokenSelect'; import { useCreateJobPageUI } from '../../../providers/CreateJobPageUIProvider'; import * as jobService from '../../../services/job'; @@ -51,6 +48,7 @@ export const CryptoPayForm = ({ const [amount, setAmount] = useState(); const [isLoading, setIsLoading] = useState(false); const [jobLauncherAddress, setJobLauncherAddress] = useState(); + const [jobLauncherFee, setJobLauncherFee] = useState(); const [minFee, setMinFee] = useState(0.01); const { data: signer } = useWalletClient(); const publicClient = usePublicClient(); @@ -59,6 +57,8 @@ export const CryptoPayForm = ({ const [fundTokenRate, setFundTokenRate] = useState(0); const [decimals, setDecimals] = useState(6); const [tokenDecimals, setTokenDecimals] = useState(18); + const onErrorRef = useRef(onError); + onErrorRef.current = onError; useEffect(() => { const fetchJobLauncherData = async () => { @@ -106,17 +106,30 @@ export const CryptoPayForm = ({ setDecimals(Math.min(tokenDecimals, 6)); }, [tokenDecimals]); - const { data: jobLauncherFee } = useReadContract({ - address: NETWORKS[jobRequest.chainId!]?.kvstoreAddress as Address, - abi: KVStoreABI, - functionName: 'get', - args: jobLauncherAddress - ? [jobLauncherAddress, KVStoreKeys.fee] - : undefined, - query: { - enabled: !!jobLauncherAddress, - }, - }); + useEffect(() => { + if (!signer || !jobLauncherAddress || !jobRequest.chainId) return; + + let ignore = false; + + const fetchJobLauncherFee = async () => { + const fee = await readContract(signer, { + address: NETWORKS[jobRequest.chainId!]?.kvstoreAddress as Address, + abi: KVStoreABI, + functionName: 'get', + args: [jobLauncherAddress, KVStoreKeys.fee], + }); + + if (!ignore) { + setJobLauncherFee(fee as string); + } + }; + + fetchJobLauncherFee().catch(onErrorRef.current); + + return () => { + ignore = true; + }; + }, [jobLauncherAddress, jobRequest.chainId, signer]); const minFeeToken = useMemo(() => { if (minFee && paymentTokenRate) @@ -125,7 +138,7 @@ export const CryptoPayForm = ({ }, [minFee, paymentTokenRate]); const feeAmount = useMemo(() => { - if (!amount) return 0; + if (!amount || jobLauncherFee == null) return 0; const amountDecimal = new Decimal(amount); const feeDecimal = new Decimal(jobLauncherFee as string).div(100); return Number( @@ -257,6 +270,18 @@ export const CryptoPayForm = ({ ); + if (!jobLauncherAddress || jobLauncherFee == null) + return ( + + + + ); + return ( @@ -476,6 +501,7 @@ export const CryptoPayForm = ({ !paymentTokenAddress || !fundTokenSymbol || !amount || + jobLauncherFee == null || jobRequest.chainId !== chain?.id } loading={isLoading} diff --git a/packages/apps/job-launcher/client/src/providers/WagmiProvider.tsx b/packages/apps/job-launcher/client/src/providers/WagmiProvider.tsx index ad9308ed9e..f63c653823 100644 --- a/packages/apps/job-launcher/client/src/providers/WagmiProvider.tsx +++ b/packages/apps/job-launcher/client/src/providers/WagmiProvider.tsx @@ -4,10 +4,11 @@ import { WagmiProvider as WWagmiProvider, fallback, http, + injected, unstable_connector, } from 'wagmi'; import * as wagmiChains from 'wagmi/chains'; -import { coinbaseWallet, walletConnect, metaMask } from 'wagmi/connectors'; +import { coinbaseWallet, walletConnect } from 'wagmi/connectors'; import { LOCALHOST } from '../constants/chains'; @@ -37,41 +38,41 @@ export const wagmiConfig = createConfig({ coinbaseWallet({ appName: 'human-job-launcher', }), - metaMask(), + injected({ target: 'metaMask' }), ], transports: { - [wagmiChains.mainnet.id]: fallback([unstable_connector(metaMask), http()]), - [wagmiChains.sepolia.id]: fallback([unstable_connector(metaMask), http()]), - [wagmiChains.bsc.id]: fallback([unstable_connector(metaMask), http()]), + [wagmiChains.mainnet.id]: fallback([unstable_connector(injected), http()]), + [wagmiChains.sepolia.id]: fallback([unstable_connector(injected), http()]), + [wagmiChains.bsc.id]: fallback([unstable_connector(injected), http()]), [wagmiChains.bscTestnet.id]: fallback([ - unstable_connector(metaMask), + unstable_connector(injected), http(), ]), - [wagmiChains.polygon.id]: fallback([unstable_connector(metaMask), http()]), + [wagmiChains.polygon.id]: fallback([unstable_connector(injected), http()]), [wagmiChains.polygonAmoy.id]: fallback([ - unstable_connector(metaMask), + unstable_connector(injected), http(), ]), - [wagmiChains.moonbeam.id]: fallback([unstable_connector(metaMask), http()]), + [wagmiChains.moonbeam.id]: fallback([unstable_connector(injected), http()]), [wagmiChains.moonbaseAlpha.id]: fallback([ - unstable_connector(metaMask), + unstable_connector(injected), http(), ]), [wagmiChains.avalanche.id]: fallback([ - unstable_connector(metaMask), + unstable_connector(injected), http(), ]), [wagmiChains.avalancheFuji.id]: fallback([ - unstable_connector(metaMask), + unstable_connector(injected), http(), ]), - [wagmiChains.xLayer.id]: fallback([unstable_connector(metaMask), http()]), + [wagmiChains.xLayer.id]: fallback([unstable_connector(injected), http()]), [wagmiChains.xLayerTestnet.id]: fallback([ - unstable_connector(metaMask), + unstable_connector(injected), http(), ]), [LOCALHOST.id]: fallback([ - unstable_connector(metaMask), + unstable_connector(injected), http(LOCALHOST.rpcUrls.default.http[0]), ]), }, From ab56e5268bc94f84f790a83ee3742288651fdf61 Mon Sep 17 00:00:00 2001 From: portuu3 <> Date: Wed, 29 Jul 2026 13:21:49 +0200 Subject: [PATCH 2/3] fee error handling for fiat payments --- .../components/Jobs/Create/CryptoPayForm.tsx | 94 ++++++++++++++----- 1 file changed, 72 insertions(+), 22 deletions(-) diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx index 70e216b97e..73dc6542df 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx @@ -18,7 +18,7 @@ import { } from '@mui/material'; import { Decimal } from 'decimal.js'; import { ethers } from 'ethers'; -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Address } from 'viem'; import { readContract } from 'viem/actions'; import { useAccount, useWalletClient, usePublicClient } from 'wagmi'; @@ -49,6 +49,10 @@ export const CryptoPayForm = ({ const [isLoading, setIsLoading] = useState(false); const [jobLauncherAddress, setJobLauncherAddress] = useState(); const [jobLauncherFee, setJobLauncherFee] = useState(); + const [configurationError, setConfigurationError] = useState(); + const [configurationRetry, setConfigurationRetry] = useState(0); + const [feeError, setFeeError] = useState(); + const [feeRetry, setFeeRetry] = useState(0); const [minFee, setMinFee] = useState(0.01); const { data: signer } = useWalletClient(); const publicClient = usePublicClient(); @@ -57,19 +61,24 @@ export const CryptoPayForm = ({ const [fundTokenRate, setFundTokenRate] = useState(0); const [decimals, setDecimals] = useState(6); const [tokenDecimals, setTokenDecimals] = useState(18); - const onErrorRef = useRef(onError); - onErrorRef.current = onError; useEffect(() => { const fetchJobLauncherData = async () => { - const address = await paymentService.getOperatorAddress(); - const fee = await paymentService.getFee(); - setJobLauncherAddress(address); - setMinFee(fee); + setConfigurationError(undefined); + try { + const address = await paymentService.getOperatorAddress(); + const fee = await paymentService.getFee(); + setJobLauncherAddress(address); + setMinFee(fee); + } catch { + setConfigurationError( + 'Unable to load the payment configuration. Please try again.', + ); + } }; fetchJobLauncherData(); - }, []); + }, [configurationRetry]); useEffect(() => { const fetchRates = async () => { @@ -107,29 +116,45 @@ export const CryptoPayForm = ({ }, [tokenDecimals]); useEffect(() => { - if (!signer || !jobLauncherAddress || !jobRequest.chainId) return; + if (!signer || !publicClient || !jobLauncherAddress || !jobRequest.chainId) + return; let ignore = false; const fetchJobLauncherFee = async () => { - const fee = await readContract(signer, { - address: NETWORKS[jobRequest.chainId!]?.kvstoreAddress as Address, - abi: KVStoreABI, - functionName: 'get', - args: [jobLauncherAddress, KVStoreKeys.fee], - }); + setFeeError(undefined); + setJobLauncherFee(undefined); + try { + const parameters = { + address: NETWORKS[jobRequest.chainId!]?.kvstoreAddress as Address, + abi: KVStoreABI, + functionName: 'get', + args: [jobLauncherAddress, KVStoreKeys.fee], + } as const; + + let fee: string; + try { + fee = (await readContract(signer, parameters)) as string; + } catch { + fee = (await readContract(publicClient, parameters)) as string; + } - if (!ignore) { - setJobLauncherFee(fee as string); + if (!ignore) { + setJobLauncherFee(fee); + } + } catch { + if (!ignore) { + setFeeError('Unable to load the job launcher fee. Please try again.'); + } } }; - fetchJobLauncherFee().catch(onErrorRef.current); + fetchJobLauncherFee(); return () => { ignore = true; }; - }, [jobLauncherAddress, jobRequest.chainId, signer]); + }, [feeRetry, jobLauncherAddress, jobRequest.chainId, publicClient, signer]); const minFeeToken = useMemo(() => { if (minFee && paymentTokenRate) @@ -257,7 +282,7 @@ export const CryptoPayForm = ({ } }; - if (!chain || chain.id !== jobRequest.chainId) + if (isConnected && (!chain || chain.id !== jobRequest.chainId)) return ( @@ -270,7 +295,32 @@ export const CryptoPayForm = ({ ); - if (!jobLauncherAddress || jobLauncherFee == null) + if (configurationError || feeError) + return ( + + + {configurationError || feeError} + + + + ); + + if ( + !jobLauncherAddress || + (isConnected && (!signer || jobLauncherFee == null)) + ) return ( Fee - ({Number(jobLauncherFee)}%){' '} + ({jobLauncherFee == null ? '—' : Number(jobLauncherFee)}%){' '} {paymentTokenSymbol ? `${Number(feeAmount.toFixed(6))} ${paymentTokenSymbol?.toUpperCase()}` : ''} From 19005990007021a6b064552e1de2a5475ae7b4c3 Mon Sep 17 00:00:00 2001 From: portuu3 <> Date: Wed, 29 Jul 2026 13:28:19 +0200 Subject: [PATCH 3/3] add RPC fallback and fee-loading error handling to fiat payment --- .../components/Jobs/Create/FiatPayForm.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx index 82502c9b9b..b6164da092 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx @@ -2,6 +2,7 @@ import KVStoreABI from '@human-protocol/core/abis/KVStore.json'; import { KVStoreKeys, NETWORKS } from '@human-protocol/sdk'; import { LoadingButton } from '@mui/lab'; import { + Alert, Box, Button, Checkbox, @@ -142,6 +143,8 @@ export const FiatPayForm = ({ data: jobLauncherFee, error, isError, + isFetching: isFeeFetching, + refetch: refetchJobLauncherFee, } = useReadContract({ address: NETWORKS[jobRequest.chainId!]?.kvstoreAddress as Address, abi: KVStoreABI, @@ -276,7 +279,9 @@ export const FiatPayForm = ({ return ( - {loadingInitialData ? ( + {loadingInitialData || + !jobLauncherAddress || + (!isError && (isFeeFetching || jobLauncherFee == null)) ? ( + ) : isError ? ( + + + Unable to load the job launcher fee from MetaMask or the public + network provider. + + + ) : ( @@ -522,6 +537,8 @@ export const FiatPayForm = ({ !amount || (!payWithAccountBalance && !selectedCard) || hasError || + isFeeFetching || + jobLauncherFee == null || !tokenSymbol } >