Skip to content

Commit 8b2b0ee

Browse files
authored
refactor: update token decimals handling and improve input validation in payment forms (#3198)
* refactor: update token decimals handling and improve input validation in payment forms * refactor: enhance payment forms with dynamic decimal handling and validation
1 parent 098abfc commit 8b2b0ee

File tree

14 files changed

+281
-96
lines changed

14 files changed

+281
-96
lines changed

packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx

Lines changed: 74 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ export const CryptoPayForm = ({
5757
const { user } = useAppSelector((state) => state.auth);
5858
const [paymentTokenRate, setPaymentTokenRate] = useState<number>(0);
5959
const [fundTokenRate, setFundTokenRate] = useState<number>(0);
60+
const [decimals, setDecimals] = useState<number>(6);
61+
const [tokenDecimals, setTokenDecimals] = useState<number>(18);
6062

6163
useEffect(() => {
6264
const fetchJobLauncherData = async () => {
@@ -91,6 +93,19 @@ export const CryptoPayForm = ({
9193
fetchRates();
9294
}, [paymentTokenSymbol, fundTokenSymbol]);
9395

96+
useEffect(() => {
97+
if (amount) {
98+
const [integerPart, decimalPart] = amount.split('.');
99+
if (decimalPart && decimalPart.length > decimals) {
100+
setAmount(`${integerPart}.${decimalPart.slice(0, decimals)}`);
101+
}
102+
}
103+
}, [decimals, amount]);
104+
105+
useEffect(() => {
106+
setDecimals(Math.min(tokenDecimals, 6));
107+
}, [tokenDecimals]);
108+
94109
const { data: jobLauncherFee } = useReadContract({
95110
address: NETWORKS[jobRequest.chainId!]?.kvstoreAddress as Address,
96111
abi: KVStoreABI,
@@ -113,13 +128,17 @@ export const CryptoPayForm = ({
113128
if (!amount) return 0;
114129
const amountDecimal = new Decimal(amount);
115130
const feeDecimal = new Decimal(jobLauncherFee as string).div(100);
116-
return Decimal.max(minFeeToken, amountDecimal.mul(feeDecimal)).toNumber();
117-
}, [amount, minFeeToken, jobLauncherFee]);
131+
return Number(
132+
Decimal.max(minFeeToken, amountDecimal.mul(feeDecimal)).toFixed(decimals),
133+
);
134+
}, [amount, jobLauncherFee, minFeeToken, decimals]);
118135

119136
const totalAmount = useMemo(() => {
120137
if (!amount) return 0;
121-
return new Decimal(amount).plus(feeAmount).toNumber();
122-
}, [amount, feeAmount]);
138+
return Number(
139+
new Decimal(amount).plus(feeAmount).toNumber().toFixed(decimals),
140+
);
141+
}, [amount, decimals, feeAmount]);
123142

124143
const totalUSDAmount = useMemo(() => {
125144
if (!totalAmount || !paymentTokenRate) return 0;
@@ -135,7 +154,7 @@ export const CryptoPayForm = ({
135154

136155
const fundAmount = useMemo(() => {
137156
if (!amount || !conversionRate) return 0;
138-
return new Decimal(amount).mul(conversionRate).toNumber();
157+
return Number(new Decimal(amount).mul(conversionRate));
139158
}, [amount, conversionRate]);
140159

141160
const currentBalance = useMemo(() => {
@@ -146,24 +165,19 @@ export const CryptoPayForm = ({
146165
);
147166
}, [user, paymentTokenSymbol]);
148167

149-
const accountAmount = useMemo(
150-
() => new Decimal(currentBalance),
151-
[currentBalance],
152-
);
153-
154168
const balancePayAmount = useMemo(() => {
155-
if (!payWithAccountBalance) return new Decimal(0);
169+
if (!payWithAccountBalance) return 0;
156170
const totalAmountDecimal = new Decimal(totalAmount);
157-
if (totalAmountDecimal.lessThan(accountAmount)) return totalAmountDecimal;
158-
return accountAmount;
159-
}, [payWithAccountBalance, totalAmount, accountAmount]);
171+
if (totalAmountDecimal.lessThan(currentBalance)) return totalAmountDecimal;
172+
return currentBalance;
173+
}, [payWithAccountBalance, totalAmount, currentBalance]);
160174

161175
const walletPayAmount = useMemo(() => {
162-
if (!payWithAccountBalance) return new Decimal(totalAmount);
176+
if (!payWithAccountBalance) return totalAmount;
163177
const totalAmountDecimal = new Decimal(totalAmount);
164-
if (totalAmountDecimal.lessThan(accountAmount)) return new Decimal(0);
165-
return totalAmountDecimal.minus(accountAmount);
166-
}, [payWithAccountBalance, totalAmount, accountAmount]);
178+
if (totalAmountDecimal.lessThan(currentBalance)) return 0;
179+
return Number(totalAmountDecimal.minus(currentBalance));
180+
}, [payWithAccountBalance, totalAmount, currentBalance]);
167181

168182
const handlePay = async () => {
169183
if (
@@ -176,14 +190,14 @@ export const CryptoPayForm = ({
176190
) {
177191
setIsLoading(true);
178192
try {
179-
if (walletPayAmount.greaterThan(0)) {
193+
if (walletPayAmount > 0) {
180194
const hash = await signer.writeContract({
181195
address: paymentTokenAddress as Address,
182196
abi: HMTokenABI,
183197
functionName: 'transfer',
184198
args: [
185199
jobLauncherAddress,
186-
ethers.parseUnits(walletPayAmount.toString(), 18),
200+
ethers.parseUnits(walletPayAmount.toString(), tokenDecimals),
187201
],
188202
});
189203

@@ -294,16 +308,32 @@ export const CryptoPayForm = ({
294308
value={paymentTokenSymbol}
295309
label={'Payment token'}
296310
labelId={'payment-token'}
297-
onTokenChange={(symbol, address) => {
311+
onTokenChange={(symbol, address, decimals) => {
298312
setPaymentTokenSymbol(symbol);
299313
setPaymentTokenAddress(address);
314+
setTokenDecimals(decimals);
315+
if (amount) {
316+
const maxDecimals = Math.min(decimals, 6);
317+
const [integerPart, decimalPart] = amount.split('.');
318+
if (decimalPart && decimalPart.length > maxDecimals) {
319+
setAmount(
320+
`${integerPart}.${decimalPart.slice(0, maxDecimals)}`,
321+
);
322+
}
323+
}
300324
}}
301325
/>
302326
<FormControl fullWidth>
303327
<TextField
304328
value={amount}
305329
type="number"
306-
onChange={(e) => setAmount(e.target.value as string)}
330+
onChange={(e) => {
331+
let value = e.target.value;
332+
const regex = new RegExp(`^\\d*\\.?\\d{0,${decimals}}$`);
333+
if (regex.test(value)) {
334+
setAmount(value);
335+
}
336+
}}
307337
placeholder="Amount"
308338
/>
309339
</FormControl>
@@ -333,8 +363,9 @@ export const CryptoPayForm = ({
333363
>
334364
<Typography>Balance</Typography>
335365
<Typography color="text.secondary">
336-
~ {currentBalance?.toFixed(2) ?? '0'}{' '}
337-
{paymentTokenSymbol?.toUpperCase() ?? 'HMT'}
366+
{paymentTokenSymbol
367+
? `${Number(currentBalance?.toFixed(6))} ${paymentTokenSymbol?.toUpperCase()}`
368+
: ''}
338369
</Typography>
339370
</Box>
340371
<Box
@@ -351,7 +382,9 @@ export const CryptoPayForm = ({
351382
>
352383
<Typography>Amount</Typography>
353384
<Typography color="text.secondary">
354-
{amount} {paymentTokenSymbol?.toUpperCase() ?? 'HMT'}
385+
{paymentTokenSymbol
386+
? `${amount} ${paymentTokenSymbol?.toUpperCase()}`
387+
: ''}
355388
</Typography>
356389
</Stack>
357390
<Stack
@@ -361,8 +394,10 @@ export const CryptoPayForm = ({
361394
>
362395
<Typography>Fee</Typography>
363396
<Typography color="text.secondary">
364-
({Number(jobLauncherFee)}%) {feeAmount}{' '}
365-
{paymentTokenSymbol?.toUpperCase() ?? 'HMT'}
397+
({Number(jobLauncherFee)}%){' '}
398+
{paymentTokenSymbol
399+
? `${Number(feeAmount.toFixed(6))} ${paymentTokenSymbol?.toUpperCase()}`
400+
: ''}
366401
</Typography>
367402
</Stack>
368403
<Stack
@@ -372,8 +407,9 @@ export const CryptoPayForm = ({
372407
>
373408
<Typography>Total payment</Typography>
374409
<Typography>
375-
{totalAmount} {paymentTokenSymbol?.toUpperCase() ?? 'HMT'}{' '}
376-
{`(~${totalUSDAmount.toFixed(2)} USD)`}
410+
{paymentTokenSymbol
411+
? `${Number(totalAmount?.toFixed(6))} ${paymentTokenSymbol?.toUpperCase()} (~${totalUSDAmount.toFixed(2)} USD)`
412+
: ''}
377413
</Typography>
378414
</Stack>
379415
</Stack>
@@ -388,8 +424,9 @@ export const CryptoPayForm = ({
388424
>
389425
<Typography color="text.secondary">Balance</Typography>
390426
<Typography color="text.secondary">
391-
{balancePayAmount.toString()}{' '}
392-
{paymentTokenSymbol?.toUpperCase() ?? 'HMT'}
427+
{paymentTokenSymbol
428+
? `${Number(balancePayAmount?.toFixed(6))} ${paymentTokenSymbol?.toUpperCase()}`
429+
: ''}
393430
</Typography>
394431
</Stack>
395432
<Stack
@@ -399,8 +436,9 @@ export const CryptoPayForm = ({
399436
>
400437
<Typography color="text.secondary">Crypto Wallet</Typography>
401438
<Typography color="text.secondary">
402-
{walletPayAmount.toString()}{' '}
403-
{paymentTokenSymbol?.toUpperCase() ?? 'HMT'}
439+
{paymentTokenSymbol
440+
? `${Number(walletPayAmount?.toFixed(6))} ${paymentTokenSymbol?.toUpperCase()}`
441+
: ''}
404442
</Typography>
405443
</Stack>
406444
</Stack>
@@ -415,7 +453,9 @@ export const CryptoPayForm = ({
415453
>
416454
<Typography>Fund Amount</Typography>
417455
<Typography color="text.secondary">
418-
{fundAmount} {fundTokenSymbol?.toUpperCase() ?? 'HMT'}
456+
{fundTokenSymbol && fundAmount
457+
? `${Number(fundAmount?.toFixed(6))} ${fundTokenSymbol?.toUpperCase()}`
458+
: ''}
419459
</Typography>
420460
</Box>
421461
</Box>

packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export const FiatPayForm = ({
9595
useEffect(() => {
9696
const fetchRates = async () => {
9797
if (tokenSymbol) {
98-
const rate = await getRate(tokenSymbol, 'usd');
98+
const rate = await getRate('usd', tokenSymbol);
9999
setTokenRate(rate);
100100
}
101101
};
@@ -176,7 +176,6 @@ export const FiatPayForm = ({
176176
(jobLauncherFee as string) || 0,
177177
).div(100);
178178
const minFeeDecimal = new Decimal(minFee || 0);
179-
180179
const fundAmountDecimal = amountDecimal.mul(tokenRateDecimal);
181180
setFundAmount(fundAmountDecimal.toNumber());
182181

@@ -329,7 +328,12 @@ export const FiatPayForm = ({
329328
variant="outlined"
330329
value={amount}
331330
type="number"
332-
onChange={(e) => setAmount(e.target.value)}
331+
onChange={(e) => {
332+
let value = e.target.value;
333+
if (/^\d*\.?\d{0,2}$/.test(value)) {
334+
setAmount(value);
335+
}
336+
}}
333337
sx={{ mb: 2 }}
334338
/>
335339
{selectedCard ? (
@@ -402,7 +406,7 @@ export const FiatPayForm = ({
402406
<Typography>Account Balance</Typography>
403407
{user?.balance && (
404408
<Typography color="text.secondary">
405-
{currentBalance.toFixed(2)} USD
409+
{Number(currentBalance.toFixed(6))} USD
406410
</Typography>
407411
)}
408412
</Box>
@@ -420,7 +424,9 @@ export const FiatPayForm = ({
420424
>
421425
<Typography>Amount</Typography>
422426
<Typography color="text.secondary">
423-
{amount} HMT
427+
{amount
428+
? `${Number(Number(amount)?.toFixed(6))} USD`
429+
: ''}
424430
</Typography>
425431
</Stack>
426432
<Stack
@@ -434,7 +440,10 @@ export const FiatPayForm = ({
434440
{Number(jobLauncherFee) >= 0
435441
? `${Number(jobLauncherFee)}%`
436442
: 'loading...'}
437-
) {feeAmount.toFixed(2)} USD
443+
){' '}
444+
{amount && feeAmount
445+
? `${Number(feeAmount?.toFixed(6))} USD`
446+
: ''}
438447
</Typography>
439448
</Stack>
440449
<Stack
@@ -443,7 +452,11 @@ export const FiatPayForm = ({
443452
alignItems="center"
444453
>
445454
<Typography>Total payment</Typography>
446-
<Typography>{totalAmount.toFixed(2)} USD</Typography>
455+
<Typography>
456+
{amount && totalAmount
457+
? `${Number(totalAmount?.toFixed(6))} USD`
458+
: ''}
459+
</Typography>
447460
</Stack>
448461
</Stack>
449462
</Box>
@@ -457,7 +470,9 @@ export const FiatPayForm = ({
457470
>
458471
<Typography color="text.secondary">Balance</Typography>
459472
<Typography color="text.secondary">
460-
{balancePayAmount.toFixed(2)} USD
473+
{amount
474+
? `${Number(balancePayAmount?.toFixed(6))} USD`
475+
: ''}
461476
</Typography>
462477
</Stack>
463478
<Stack
@@ -469,7 +484,9 @@ export const FiatPayForm = ({
469484
Credit Card
470485
</Typography>
471486
<Typography color="text.secondary">
472-
{creditCardPayAmount.toFixed(2)} USD
487+
{amount && creditCardPayAmount
488+
? `${Number(creditCardPayAmount?.toFixed(6))} USD`
489+
: ''}
473490
</Typography>
474491
</Stack>
475492
</Stack>
@@ -485,7 +502,9 @@ export const FiatPayForm = ({
485502
>
486503
<Typography>Fund Amount</Typography>
487504
<Typography color="text.secondary">
488-
{fundAmount} {tokenSymbol?.toUpperCase() ?? 'HMT'}
505+
{tokenSymbol && fundAmount
506+
? `${Number(fundAmount?.toFixed(6))} ${tokenSymbol?.toUpperCase()}`
507+
: ''}
489508
</Typography>
490509
</Box>
491510
</Box>

packages/apps/job-launcher/client/src/components/TokenSelect/index.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ import * as paymentService from '../../services/payment';
1313

1414
type TokenSelectProps = SelectProps & {
1515
chainId: ChainId;
16-
onTokenChange: (symbol: string, address: string) => void;
16+
onTokenChange: (symbol: string, address: string, decimals: number) => void;
1717
};
1818

1919
export const TokenSelect: FC<TokenSelectProps> = (props) => {
2020
const [availableTokens, setAvailableTokens] = useState<{
21-
[key: string]: string;
21+
[key: string]: {
22+
address: string;
23+
decimals: number;
24+
};
2225
}>({});
2326

2427
useEffect(() => {
@@ -52,8 +55,11 @@ export const TokenSelect: FC<TokenSelectProps> = (props) => {
5255
{...props}
5356
onChange={(e) => {
5457
const symbol = e.target.value as string;
55-
const address = availableTokens[symbol];
56-
props.onTokenChange(symbol, address);
58+
props.onTokenChange(
59+
symbol,
60+
availableTokens[symbol].address,
61+
availableTokens[symbol].decimals,
62+
);
5763
}}
5864
>
5965
{Object.keys(availableTokens).map((symbol) => {

0 commit comments

Comments
 (0)