From 8345648d6132d47f20300f2db0585f65468c9f11 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Thu, 12 Mar 2026 21:11:03 +0000 Subject: [PATCH] fix(rcart): fix floating-point precision in wallet payment amounts Replace parseFloat * 10^decimals with string-based decimal parsing to avoid precision loss for 18-decimal tokens (ETH). Affects both MetaMask and EncryptID payment paths. Co-Authored-By: Claude Opus 4.6 --- modules/rcart/components/folk-payment-page.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/modules/rcart/components/folk-payment-page.ts b/modules/rcart/components/folk-payment-page.ts index 8a6e974..8b20c64 100644 --- a/modules/rcart/components/folk-payment-page.ts +++ b/modules/rcart/components/folk-payment-page.ts @@ -216,7 +216,7 @@ class FolkPaymentPage extends HTMLElement { if (p.token === 'ETH') { // Native ETH transfer - const weiAmount = BigInt(Math.round(parseFloat(effectiveAmount) * 1e18)); + const weiAmount = this.parseTokenAmount(effectiveAmount, 18); txHash = await signer.sendTransaction({ from: this.walletAccount, to: p.recipientAddress, @@ -229,7 +229,7 @@ class FolkPaymentPage extends HTMLElement { if (!usdcAddress) throw new Error('USDC not supported on this chain'); const decimals = p.token === 'USDC' ? 6 : 18; - const rawAmount = BigInt(Math.round(parseFloat(effectiveAmount) * (10 ** decimals))); + const rawAmount = this.parseTokenAmount(effectiveAmount, decimals); // transfer(address to, uint256 amount) — selector: 0xa9059cbb const recipient = p.recipientAddress.slice(2).toLowerCase().padStart(64, '0'); @@ -303,7 +303,7 @@ class FolkPaymentPage extends HTMLElement { txHash = await client.sendTransaction({ account, to: p.recipientAddress as `0x${string}`, - value: BigInt(Math.round(parseFloat(effectiveAmount) * 1e18)), + value: this.parseTokenAmount(effectiveAmount, 18), chain, }); } else { @@ -312,7 +312,7 @@ class FolkPaymentPage extends HTMLElement { if (!usdcAddress) throw new Error('USDC not supported on this chain'); const decimals = p.token === 'USDC' ? 6 : 18; - const rawAmount = BigInt(Math.round(parseFloat(effectiveAmount) * (10 ** decimals))); + const rawAmount = this.parseTokenAmount(effectiveAmount, decimals); const recipient = p.recipientAddress.slice(2).toLowerCase().padStart(64, '0'); const amountHex = rawAmount.toString(16).padStart(64, '0'); @@ -347,6 +347,18 @@ class FolkPaymentPage extends HTMLElement { return this.customAmount || '0'; } + /** + * Parse a decimal amount string into a BigInt with the given decimals. + * Avoids floating-point precision loss for 18-decimal tokens. + * e.g. parseTokenAmount("1.5", 18) → 1500000000000000000n + */ + private parseTokenAmount(amount: string, decimals: number): bigint { + const parts = amount.split('.'); + const whole = parts[0] || '0'; + let frac = (parts[1] || '').slice(0, decimals).padEnd(decimals, '0'); + return BigInt(whole) * BigInt(10 ** decimals) + BigInt(frac); + } + // ── Status update ── private async updatePaymentStatus(status: string, method: string, txHash?: string | null, transakOrderId?: string | null) {