Skip to content

Commit 8c0a033

Browse files
Truncate decimals when sending kaspa and krc20
1 parent 3f968ea commit 8c0a033

File tree

6 files changed

+86
-26
lines changed

6 files changed

+86
-26
lines changed

src/hooks/useTransactionInputs.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { useState } from 'react'
22
import { validateRecipient, validateAmountToSend } from '@/utils/validation'
3-
import { formatAndValidateAmount } from '@/utils/formatting'
3+
import { truncateDecimals } from '@/utils/formatting'
4+
import { AccountToken } from '@/types/interfaces'
45

5-
export const useTransactionInputs = (token: any, maxAmount: string, yourAddress: string) => {
6+
export const useTransactionInputs = (token: AccountToken, maxAmount: number, yourAddress: string) => {
67
const [outputs, setOutputs] = useState<[string, string][]>([['', '']])
78
const [recipientError, setRecipientError] = useState<string | null>(null)
89
const [amountError, setAmountError] = useState<string | null>(null)
@@ -18,14 +19,14 @@ export const useTransactionInputs = (token: any, maxAmount: string, yourAddress:
1819
}
1920

2021
const handleAmountChange = (value: string) => {
21-
formatAndValidateAmount(value, token.dec)
22+
const truncatedValue = truncateDecimals(value, Number(token.dec))
2223
setOutputs((prevOutputs) => {
2324
const newOutputs = [...prevOutputs]
24-
newOutputs[0][1] = value
25+
newOutputs[0][1] = truncatedValue
2526
return newOutputs
2627
})
2728

28-
validateAmountToSend(token.tick, value, parseFloat(maxAmount), setAmountError)
29+
validateAmountToSend(token.tick, truncatedValue, maxAmount, setAmountError)
2930
}
3031

3132
const handleMaxClick = () => {

src/hooks/wallet/useWalletTokens.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import useKaspa from '@/hooks/contexts/useKaspa'
55
import { usePrices } from '@/hooks/ghost/usePrice'
66
import { isKrc20QueryEnabled, useKrc20TokensQuery } from '@/hooks/kasplex/fetchKrc20AddressTokenList'
77
import { useKsprPrices } from '@/hooks/kspr/fetchKsprPrices'
8-
import { AccountKaspaToken, AccountTokenFromApi, AccountToken, AccountTokenWithPrices } from '@/types/interfaces'
8+
import { AccountKaspaToken, AccountToken, AccountTokenWithPrices } from '@/types/interfaces'
99
import { useKasFyiMarketData } from '@/hooks/kas-fyi/fetchMarketData'
1010

1111
export function useWalletTokens() {
@@ -80,7 +80,7 @@ export function useWalletTokens() {
8080
}
8181
})
8282

83-
return [kaspaCrypto, ...tokensWithPrices] as (AccountKaspaToken | AccountToken)[]
83+
return [kaspaCrypto, ...tokensWithPrices]
8484
}, [kaspaCrypto, krc20TokensData, kasFyiMarketData, ksprPricesData, kasPrice])
8585

8686
const sortedTokens = sortTokensByValue(tokens)

src/pages/Wallet/Send/InitiateSend.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,20 @@ import AmountInput from '@/components/inputs/AmountInput'
99
import NextButton from '@/components/buttons/NextButton'
1010
import useKaspa from '@/hooks/contexts/useKaspa'
1111
import { useTransactionInputs } from '@/hooks/useTransactionInputs'
12-
import { formatNumberWithDecimal, formatTokenBalance } from '@/utils/formatting'
12+
import { formatAccountTokenBalance, formatNumberWithDecimal, formatTokenBalance } from '@/utils/formatting'
1313
import useSettings from '@/hooks/contexts/useSettings'
1414
import CryptoImage from '@/components/CryptoImage'
1515
import TopNav from '@/components/navigation/TopNav'
1616
import ErrorMessages from '@/utils/constants/errorMessages'
1717
import { MINIMUM_KAS_FOR_GAS_FEE } from '@/utils/constants/constants'
18+
import { AccountToken } from '@/types/interfaces'
1819

1920
const InitiateSend: React.FC = () => {
2021
const location = useLocation()
2122
const navigate = useNavigate()
2223
const { request, kaspa } = useKaspa()
2324
const { settings } = useSettings()
24-
const { token } = location.state || {}
25+
const { token }: {token: AccountToken} = location.state || {}
2526

2627
const maxAmount = token.isKaspa ? token.balance : formatNumberWithDecimal(token.balance, token.dec)
2728
const { outputs, recipientError, amountError, handleRecipientChange, handleAmountChange, handleMaxClick } =
@@ -55,7 +56,7 @@ const InitiateSend: React.FC = () => {
5556
const isButtonEnabled =
5657
outputs[0][0].length > 0 && outputs[0][1].length > 0 && !recipientError && !amountError && !error
5758

58-
const formattedBalance = formatTokenBalance(token.balance, token.tick, token.dec).toLocaleString()
59+
const formattedBalance = formatAccountTokenBalance(token).toLocaleString()
5960

6061
return (
6162
<>

src/pages/Wallet/Swap/YouPaySection.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ChaingeToken } from '@/hooks/chainge/useChaingeTokens'
44
import { validateAmountToSend } from '@/utils/validation'
55
import ValueAndAvailableBalance from '@/pages/Wallet/Swap/ValueAndAvailableBalance'
66
import useChaingeTokenData from '@/hooks/chainge/useChaingeTokenData'
7-
import { formatNumberWithDecimal } from '@/utils/formatting'
7+
import { formatNumberWithDecimal, truncateDecimals } from '@/utils/formatting'
88
import { KAS_TICKER } from '@/utils/constants/tickers'
99

1010
interface YouPaySectionProps {
@@ -58,12 +58,9 @@ const YouPaySection: React.FC<YouPaySectionProps> = ({
5858
const handleAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
5959
let value = e.target.value
6060
value = value.replace(/[^0-9.]/g, '')
61-
62-
const [whole, decimals] = value.split('.')
6361
const allowedDecimals = payToken?.decimals || 0
64-
const truncatedDecimals = decimals?.slice(0, allowedDecimals)
62+
const validatedValue = truncateDecimals(value, allowedDecimals)
6563

66-
const validatedValue = truncatedDecimals !== undefined ? `${whole}.${truncatedDecimals}` : whole
6764
onAmountChange({
6865
target: { value: validatedValue },
6966
} as React.ChangeEvent<HTMLInputElement>)

src/utils/formatting.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { truncateDecimals } from "./formatting"
2+
3+
describe('truncateDecimals', () => {
4+
it('should return the whole number if no decimal', () => {
5+
const value = '1234'
6+
const decimals = 2
7+
const result = truncateDecimals(value, decimals)
8+
expect(result).toBe('1234')
9+
})
10+
11+
it('should return the whole number if decimals is 0', () => {
12+
const value = '1234.56'
13+
const decimals = 0
14+
const result = truncateDecimals(value, decimals)
15+
expect(result).toBe('1234')
16+
})
17+
18+
it('should remove decimal point if decimals = 0', () => {
19+
const value = '1234.'
20+
const decimals = 0
21+
const result = truncateDecimals(value, decimals)
22+
expect(result).toBe('1234')
23+
})
24+
25+
it('should not remove decimal point if decimals > 0', () => {
26+
const value = '1234.'
27+
const decimals = 1
28+
const result = truncateDecimals(value, decimals)
29+
expect(result).toBe('1234.')
30+
})
31+
32+
it('should return a truncated value with the specified number of decimals', () => {
33+
const value = '1234.5678'
34+
const decimals = 2
35+
const result = truncateDecimals(value, decimals)
36+
expect(result).toBe('1234.56')
37+
})
38+
39+
it('should handle having no whole number', () => {
40+
const value = '.456'
41+
const decimals = 2
42+
const result = truncateDecimals(value, decimals)
43+
expect(result).toBe('.45')
44+
})
45+
46+
it('should return "." if value is "."', () => {
47+
const value = '.'
48+
const decimals = 2
49+
const result = truncateDecimals(value, decimals)
50+
expect(result).toBe('.')
51+
})
52+
})

src/utils/formatting.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import useSettings from '@/hooks/contexts/useSettings'
2+
import { AccountToken } from '@/types/interfaces'
23
import { parseUnits } from 'ethers'
34

45
export const formatValue = (value: number | null | undefined): number => {
@@ -25,6 +26,14 @@ export const formatNumberWithDecimal = (balance: number | string, decimals: numb
2526
return parseFloat((balance / factor).toFixed(decimals))
2627
}
2728

29+
export const formatAccountTokenBalance = (token: AccountToken): number => {
30+
if (token.isKaspa) {
31+
return parseFloat(token.balance.toFixed(token.balance % 1 === 0 ? 0 : 2))
32+
} else {
33+
return formatNumberWithDecimal(token.balance, token.dec)
34+
}
35+
}
36+
2837
export const formatTokenBalance = (balance: number, tick: string, decimals: number): number => {
2938
if (tick === 'KASPA') {
3039
return parseFloat(balance.toFixed(balance % 1 === 0 ? 0 : 2))
@@ -178,17 +187,6 @@ export const formatGasFee = (gasFee: string | number): string => {
178187
})
179188
}
180189

181-
export const formatAndValidateAmount = (value: string, maxDecimals: number): string | null => {
182-
const decimalPlaces = value.split('.')[1]?.length || 0
183-
if (decimalPlaces > maxDecimals) return null
184-
185-
if (value.startsWith('.') && value.length > 1) {
186-
value = `0${value}`
187-
}
188-
189-
return value
190-
}
191-
192190
export const formatPercentage = (value: string | number): string => {
193191
const parsedValue = typeof value === 'string' ? parseFloat(value) : value
194192

@@ -213,4 +211,15 @@ export const formatUsd = (value: number): string => {
213211

214212
export function decimalToBigint(value: string, decimals: number): bigint {
215213
return parseUnits(value, decimals)
214+
}
215+
216+
export function truncateDecimals(value: string, decimals: number): string {
217+
const [whole, decimalsStr] = value.split('.')
218+
if (decimals === 0) {
219+
return whole
220+
}
221+
if (decimalsStr === undefined || decimalsStr.length === 0) {
222+
return value
223+
}
224+
return `${whole}.${decimalsStr.slice(0, decimals)}`
216225
}

0 commit comments

Comments
 (0)