Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Box,
Button,
Checkbox,
CircularProgress,
FormControl,
FormControlLabel,
Grid,
Expand All @@ -19,12 +20,8 @@ import { Decimal } from 'decimal.js';
import { ethers } from 'ethers';
import { useEffect, useMemo, 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';
Expand All @@ -51,6 +48,11 @@ export const CryptoPayForm = ({
const [amount, setAmount] = useState<string>();
const [isLoading, setIsLoading] = useState(false);
const [jobLauncherAddress, setJobLauncherAddress] = useState<string>();
const [jobLauncherFee, setJobLauncherFee] = useState<string>();
const [configurationError, setConfigurationError] = useState<string>();
const [configurationRetry, setConfigurationRetry] = useState(0);
const [feeError, setFeeError] = useState<string>();
const [feeRetry, setFeeRetry] = useState(0);
const [minFee, setMinFee] = useState<number>(0.01);
const { data: signer } = useWalletClient();
const publicClient = usePublicClient();
Expand All @@ -62,14 +64,21 @@ export const CryptoPayForm = ({

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 () => {
Expand Down Expand Up @@ -106,17 +115,46 @@ 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 || !publicClient || !jobLauncherAddress || !jobRequest.chainId)
return;

let ignore = false;

const fetchJobLauncherFee = async () => {
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);
}
} catch {
if (!ignore) {
setFeeError('Unable to load the job launcher fee. Please try again.');
}
}
};

fetchJobLauncherFee();

return () => {
ignore = true;
};
}, [feeRetry, jobLauncherAddress, jobRequest.chainId, publicClient, signer]);

const minFeeToken = useMemo(() => {
if (minFee && paymentTokenRate)
Expand All @@ -125,7 +163,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(
Expand Down Expand Up @@ -244,7 +282,7 @@ export const CryptoPayForm = ({
}
};

if (!chain || chain.id !== jobRequest.chainId)
if (isConnected && (!chain || chain.id !== jobRequest.chainId))
return (
<Box textAlign="center">
<Typography textAlign="center">
Expand All @@ -257,6 +295,43 @@ export const CryptoPayForm = ({
</Box>
);

if (configurationError || feeError)
return (
<Box textAlign="center" minHeight={400}>
<Alert severity="error" sx={{ mb: 2 }}>
{configurationError || feeError}
</Alert>
<Button
variant="contained"
onClick={() => {
if (configurationError) {
setConfigurationRetry((retry) => retry + 1);
}
if (feeError) {
setFeeRetry((retry) => retry + 1);
}
}}
>
Try again
</Button>
</Box>
);

if (
!jobLauncherAddress ||
(isConnected && (!signer || jobLauncherFee == null))
)
return (
<Box
display="flex"
justifyContent="center"
alignItems="center"
minHeight={400}
>
<CircularProgress />
</Box>
);

return (
<Box sx={{ width: '100%' }}>
<Grid container spacing={4} mb={6} sx={{ width: '100%' }}>
Expand Down Expand Up @@ -389,7 +464,7 @@ export const CryptoPayForm = ({
>
<Typography>Fee</Typography>
<Typography color="text.secondary">
({Number(jobLauncherFee)}%){' '}
({jobLauncherFee == null ? '—' : Number(jobLauncherFee)}%){' '}
{paymentTokenSymbol
? `${Number(feeAmount.toFixed(6))} ${paymentTokenSymbol?.toUpperCase()}`
: ''}
Expand Down Expand Up @@ -476,6 +551,7 @@ export const CryptoPayForm = ({
!paymentTokenAddress ||
!fundTokenSymbol ||
!amount ||
jobLauncherFee == null ||
jobRequest.chainId !== chain?.id
}
loading={isLoading}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -276,7 +279,9 @@ export const FiatPayForm = ({

return (
<Box sx={{ width: '100%' }}>
{loadingInitialData ? (
{loadingInitialData ||
!jobLauncherAddress ||
(!isError && (isFeeFetching || jobLauncherFee == null)) ? (
<Box
display="flex"
justifyContent="center"
Expand All @@ -285,6 +290,16 @@ export const FiatPayForm = ({
>
<CircularProgress />
</Box>
) : isError ? (
<Box textAlign="center" minHeight={400}>
<Alert severity="error" sx={{ mb: 2 }}>
Unable to load the job launcher fee from MetaMask or the public
network provider.
</Alert>
<Button variant="contained" onClick={() => refetchJobLauncherFee()}>
Try again
</Button>
</Box>
) : (
<Box>
<Grid container spacing={4} mb={6} sx={{ width: '100%' }}>
Expand Down Expand Up @@ -522,6 +537,8 @@ export const FiatPayForm = ({
!amount ||
(!payWithAccountBalance && !selectedCard) ||
hasError ||
isFeeFetching ||
jobLauncherFee == null ||
!tokenSymbol
}
>
Expand Down
31 changes: 16 additions & 15 deletions packages/apps/job-launcher/client/src/providers/WagmiProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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]),
]),
},
Expand Down
Loading