Skip to content

Commit

Permalink
Truncate decimals when sending kaspa and krc20
Browse files Browse the repository at this point in the history
  • Loading branch information
phantomofthechromium committed Dec 30, 2024
1 parent 3f968ea commit 8c0a033
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 26 deletions.
11 changes: 6 additions & 5 deletions src/hooks/useTransactionInputs.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useState } from 'react'
import { validateRecipient, validateAmountToSend } from '@/utils/validation'
import { formatAndValidateAmount } from '@/utils/formatting'
import { truncateDecimals } from '@/utils/formatting'
import { AccountToken } from '@/types/interfaces'

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

const handleAmountChange = (value: string) => {
formatAndValidateAmount(value, token.dec)
const truncatedValue = truncateDecimals(value, Number(token.dec))
setOutputs((prevOutputs) => {
const newOutputs = [...prevOutputs]
newOutputs[0][1] = value
newOutputs[0][1] = truncatedValue
return newOutputs
})

validateAmountToSend(token.tick, value, parseFloat(maxAmount), setAmountError)
validateAmountToSend(token.tick, truncatedValue, maxAmount, setAmountError)
}

const handleMaxClick = () => {
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/wallet/useWalletTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import useKaspa from '@/hooks/contexts/useKaspa'
import { usePrices } from '@/hooks/ghost/usePrice'
import { isKrc20QueryEnabled, useKrc20TokensQuery } from '@/hooks/kasplex/fetchKrc20AddressTokenList'
import { useKsprPrices } from '@/hooks/kspr/fetchKsprPrices'
import { AccountKaspaToken, AccountTokenFromApi, AccountToken, AccountTokenWithPrices } from '@/types/interfaces'
import { AccountKaspaToken, AccountToken, AccountTokenWithPrices } from '@/types/interfaces'
import { useKasFyiMarketData } from '@/hooks/kas-fyi/fetchMarketData'

export function useWalletTokens() {
Expand Down Expand Up @@ -80,7 +80,7 @@ export function useWalletTokens() {
}
})

return [kaspaCrypto, ...tokensWithPrices] as (AccountKaspaToken | AccountToken)[]
return [kaspaCrypto, ...tokensWithPrices]
}, [kaspaCrypto, krc20TokensData, kasFyiMarketData, ksprPricesData, kasPrice])

const sortedTokens = sortTokensByValue(tokens)
Expand Down
7 changes: 4 additions & 3 deletions src/pages/Wallet/Send/InitiateSend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,20 @@ import AmountInput from '@/components/inputs/AmountInput'
import NextButton from '@/components/buttons/NextButton'
import useKaspa from '@/hooks/contexts/useKaspa'
import { useTransactionInputs } from '@/hooks/useTransactionInputs'
import { formatNumberWithDecimal, formatTokenBalance } from '@/utils/formatting'
import { formatAccountTokenBalance, formatNumberWithDecimal, formatTokenBalance } from '@/utils/formatting'
import useSettings from '@/hooks/contexts/useSettings'
import CryptoImage from '@/components/CryptoImage'
import TopNav from '@/components/navigation/TopNav'
import ErrorMessages from '@/utils/constants/errorMessages'
import { MINIMUM_KAS_FOR_GAS_FEE } from '@/utils/constants/constants'
import { AccountToken } from '@/types/interfaces'

const InitiateSend: React.FC = () => {
const location = useLocation()
const navigate = useNavigate()
const { request, kaspa } = useKaspa()
const { settings } = useSettings()
const { token } = location.state || {}
const { token }: {token: AccountToken} = location.state || {}

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

const formattedBalance = formatTokenBalance(token.balance, token.tick, token.dec).toLocaleString()
const formattedBalance = formatAccountTokenBalance(token).toLocaleString()

return (
<>
Expand Down
7 changes: 2 additions & 5 deletions src/pages/Wallet/Swap/YouPaySection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ChaingeToken } from '@/hooks/chainge/useChaingeTokens'
import { validateAmountToSend } from '@/utils/validation'
import ValueAndAvailableBalance from '@/pages/Wallet/Swap/ValueAndAvailableBalance'
import useChaingeTokenData from '@/hooks/chainge/useChaingeTokenData'
import { formatNumberWithDecimal } from '@/utils/formatting'
import { formatNumberWithDecimal, truncateDecimals } from '@/utils/formatting'
import { KAS_TICKER } from '@/utils/constants/tickers'

interface YouPaySectionProps {
Expand Down Expand Up @@ -58,12 +58,9 @@ const YouPaySection: React.FC<YouPaySectionProps> = ({
const handleAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let value = e.target.value
value = value.replace(/[^0-9.]/g, '')

const [whole, decimals] = value.split('.')
const allowedDecimals = payToken?.decimals || 0
const truncatedDecimals = decimals?.slice(0, allowedDecimals)
const validatedValue = truncateDecimals(value, allowedDecimals)

const validatedValue = truncatedDecimals !== undefined ? `${whole}.${truncatedDecimals}` : whole
onAmountChange({
target: { value: validatedValue },
} as React.ChangeEvent<HTMLInputElement>)
Expand Down
52 changes: 52 additions & 0 deletions src/utils/formatting.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { truncateDecimals } from "./formatting"

describe('truncateDecimals', () => {
it('should return the whole number if no decimal', () => {
const value = '1234'
const decimals = 2
const result = truncateDecimals(value, decimals)
expect(result).toBe('1234')
})

it('should return the whole number if decimals is 0', () => {
const value = '1234.56'
const decimals = 0
const result = truncateDecimals(value, decimals)
expect(result).toBe('1234')
})

it('should remove decimal point if decimals = 0', () => {
const value = '1234.'
const decimals = 0
const result = truncateDecimals(value, decimals)
expect(result).toBe('1234')
})

it('should not remove decimal point if decimals > 0', () => {
const value = '1234.'
const decimals = 1
const result = truncateDecimals(value, decimals)
expect(result).toBe('1234.')
})

it('should return a truncated value with the specified number of decimals', () => {
const value = '1234.5678'
const decimals = 2
const result = truncateDecimals(value, decimals)
expect(result).toBe('1234.56')
})

it('should handle having no whole number', () => {
const value = '.456'
const decimals = 2
const result = truncateDecimals(value, decimals)
expect(result).toBe('.45')
})

it('should return "." if value is "."', () => {
const value = '.'
const decimals = 2
const result = truncateDecimals(value, decimals)
expect(result).toBe('.')
})
})
31 changes: 20 additions & 11 deletions src/utils/formatting.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import useSettings from '@/hooks/contexts/useSettings'
import { AccountToken } from '@/types/interfaces'
import { parseUnits } from 'ethers'

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

export const formatAccountTokenBalance = (token: AccountToken): number => {
if (token.isKaspa) {
return parseFloat(token.balance.toFixed(token.balance % 1 === 0 ? 0 : 2))
} else {
return formatNumberWithDecimal(token.balance, token.dec)
}
}

export const formatTokenBalance = (balance: number, tick: string, decimals: number): number => {
if (tick === 'KASPA') {
return parseFloat(balance.toFixed(balance % 1 === 0 ? 0 : 2))
Expand Down Expand Up @@ -178,17 +187,6 @@ export const formatGasFee = (gasFee: string | number): string => {
})
}

export const formatAndValidateAmount = (value: string, maxDecimals: number): string | null => {
const decimalPlaces = value.split('.')[1]?.length || 0
if (decimalPlaces > maxDecimals) return null

if (value.startsWith('.') && value.length > 1) {
value = `0${value}`
}

return value
}

export const formatPercentage = (value: string | number): string => {
const parsedValue = typeof value === 'string' ? parseFloat(value) : value

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

export function decimalToBigint(value: string, decimals: number): bigint {
return parseUnits(value, decimals)
}

export function truncateDecimals(value: string, decimals: number): string {
const [whole, decimalsStr] = value.split('.')
if (decimals === 0) {
return whole
}
if (decimalsStr === undefined || decimalsStr.length === 0) {
return value
}
return `${whole}.${decimalsStr.slice(0, decimals)}`
}

0 comments on commit 8c0a033

Please sign in to comment.