diff --git a/package-lock.json b/package-lock.json index 5fca2da4..556aae5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@etherspot/data-utils": "1.1.1", "@etherspot/intent-sdk": "1.0.0-alpha.12", "@etherspot/modular-sdk": "6.1.1", - "@etherspot/transaction-kit": "2.1.3", + "@etherspot/transaction-kit": "2.1.4", "@hypelab/sdk-react": "1.0.4", "@lifi/sdk": "3.6.8", "@mui/icons-material": "5.16.6", @@ -5302,9 +5302,9 @@ } }, "node_modules/@etherspot/transaction-kit": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@etherspot/transaction-kit/-/transaction-kit-2.1.3.tgz", - "integrity": "sha512-PccY2N8Ifri3DGiR91MA3pQHEL4dXRQZYaJIXRRqpU+Y7AvgjdPHN95CFc9uyWhsFthe12EEX0/+JBk6Z29Y+g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@etherspot/transaction-kit/-/transaction-kit-2.1.4.tgz", + "integrity": "sha512-a17dM3U1HGJtYHltxx9SRBlBClK5JaSUmqZ+Ki37vg5r+eIcEcEyRyXZVgmCsORFd1pxXU/eqfgkU6agFJmrWw==", "license": "MIT", "dependencies": { "@etherspot/eip1271-verification-util": "0.1.2", @@ -25138,6 +25138,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -37040,6 +37041,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -37064,6 +37066,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" diff --git a/package.json b/package.json index 78b272e2..b022c866 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@etherspot/data-utils": "1.1.1", "@etherspot/intent-sdk": "1.0.0-alpha.12", "@etherspot/modular-sdk": "6.1.1", - "@etherspot/transaction-kit": "2.1.3", + "@etherspot/transaction-kit": "2.1.4", "@hypelab/sdk-react": "1.0.4", "@lifi/sdk": "3.6.8", "@mui/icons-material": "5.16.6", diff --git a/src/apps/pulse/components/Buy/PayingToken.tsx b/src/apps/pulse/components/Buy/PayingToken.tsx index adf56004..6f804695 100644 --- a/src/apps/pulse/components/Buy/PayingToken.tsx +++ b/src/apps/pulse/components/Buy/PayingToken.tsx @@ -4,6 +4,7 @@ import { MdCheck } from 'react-icons/md'; // utils import { getLogoForChainId } from '../../../../utils/blockchain'; +import { truncateDecimals } from '../../utils/number'; // types import { PayingToken as PayingTokenType } from '../../types/tokens'; @@ -85,7 +86,7 @@ export default function PayingToken(props: PayingTokenProps) {
- {payingToken.totalRaw} + {truncateDecimals(payingToken.totalRaw, 6)}
${payingToken.totalUsd.toFixed(2)} diff --git a/src/apps/pulse/components/Buy/tests/Buy.test.tsx b/src/apps/pulse/components/Buy/tests/Buy.test.tsx index 5eb6e516..f5a19cc7 100644 --- a/src/apps/pulse/components/Buy/tests/Buy.test.tsx +++ b/src/apps/pulse/components/Buy/tests/Buy.test.tsx @@ -94,6 +94,11 @@ vi.mock('../../../../../services/pillarXApiTransactionsHistory', () => ({ vi.mock('../../hooks/useRelayBuy', () => ({ default: vi.fn(() => ({ + getUSDCToken: vi.fn(() => ({ + chainId: 1, + address: '0xUSDC1234567890', + decimals: 6, + })), getBestOffer: vi.fn(), isInitialized: false, error: null, diff --git a/src/apps/pulse/components/Sell/PreviewSell.tsx b/src/apps/pulse/components/Sell/PreviewSell.tsx index 41a9b0c8..b163f579 100644 --- a/src/apps/pulse/components/Sell/PreviewSell.tsx +++ b/src/apps/pulse/components/Sell/PreviewSell.tsx @@ -69,7 +69,7 @@ const PreviewSell = (props: PreviewSellProps) => { const [isTransactionSuccess, setIsTransactionSuccess] = useState(false); const previewModalRef = useRef(null); const { - getUSDCAddress, + getUSDCToken, executeSell, error, clearError, @@ -232,7 +232,8 @@ const PreviewSell = (props: PreviewSellProps) => { // } // }; - const usdcAddress = getUSDCAddress(selectedChainIdForSettlement || 0); + const usdcToken = getUSDCToken(selectedChainIdForSettlement || 0); + const usdcAddress = usdcToken ? usdcToken.address : ''; // Clean up pulse-sell batch when component unmounts or preview closes useEffect(() => { diff --git a/src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx b/src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx index ea1b5d4b..7f0c3b0c 100644 --- a/src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx +++ b/src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx @@ -162,7 +162,11 @@ describe('', () => { vi.clearAllMocks(); (useRelaySell as any).mockReturnValue({ - getUSDCAddress: vi.fn(() => '0xUSDC1234567890'), + getUSDCToken: vi.fn(() => ({ + chainId: 1, + address: '0xUSDC1234567890', + decimals: 6, + })), executeSell: vi.fn(), error: null, clearError: vi.fn(), @@ -259,7 +263,11 @@ describe('', () => { }) ); (useRelaySell as any).mockReturnValue({ - getUSDCAddress: vi.fn(() => '0xUSDC1234567890'), + getUSDCToken: vi.fn(() => ({ + chainId: 1, + address: '0xUSDC1234567890', + decimals: 6, + })), executeSell: mockExecuteSell, error: null, clearError: vi.fn(), @@ -299,7 +307,11 @@ describe('', () => { describe('handles error states', () => { it('displays relay error', () => { (useRelaySell as any).mockReturnValue({ - getUSDCAddress: vi.fn(() => '0xUSDC1234567890'), + getUSDCToken: vi.fn(() => ({ + chainId: 1, + address: '0xUSDC1234567890', + decimals: 6, + })), executeSell: vi.fn(), error: 'Relay error occurred', clearError: vi.fn(), @@ -334,7 +346,7 @@ describe('', () => { describe('handles edge cases', () => { it('handles missing USDC address', () => { (useRelaySell as any).mockReturnValue({ - getUSDCAddress: vi.fn(() => null), + getUSDCToken: vi.fn(() => null), executeSell: vi.fn(), error: null, clearError: vi.fn(), diff --git a/src/apps/pulse/constants/tokens.ts b/src/apps/pulse/constants/tokens.ts index 2dedd40f..b9afcb33 100644 --- a/src/apps/pulse/constants/tokens.ts +++ b/src/apps/pulse/constants/tokens.ts @@ -1,13 +1,41 @@ import { isGnosisEnabled } from '../../../utils/blockchain'; export const allStableCurrencies = [ - { chainId: 1, address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' }, - { chainId: 10, address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85' }, // USDC on Optimism - { chainId: 137, address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359' }, // USDC on Polygon - { chainId: 8453, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' }, // USDC on Base - { chainId: 42161, address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' }, // USDC on Arbitrum - { chainId: 56, address: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d' }, // USDC on BNB Smart Chain - { chainId: 100, address: '0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0' }, // USDC on Gnosis + { + chainId: 1, + address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + decimals: 6, + }, + { + chainId: 10, + address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', + decimals: 6, + }, // USDC on Optimism + { + chainId: 137, + address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', + decimals: 6, + }, // USDC on Polygon + { + chainId: 8453, + address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + decimals: 6, + }, // USDC on Base + { + chainId: 42161, + address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + decimals: 6, + }, // USDC on Arbitrum + { + chainId: 56, + address: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', + decimals: 18, + }, // USDC on BNB Smart Chain + { + chainId: 100, + address: '0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0', + decimals: 6, + }, // USDC on Gnosis ]; export const STABLE_CURRENCIES = allStableCurrencies.filter( diff --git a/src/apps/pulse/hooks/tests/useRelaySell.test.tsx b/src/apps/pulse/hooks/tests/useRelaySell.test.tsx index 97736dc6..7d3eadb7 100644 --- a/src/apps/pulse/hooks/tests/useRelaySell.test.tsx +++ b/src/apps/pulse/hooks/tests/useRelaySell.test.tsx @@ -53,7 +53,7 @@ vi.mock('../../constants/tokens', () => ({ { chainId: 1, address: '0xA0b86a33E6441b8C4C8C0C8C0C8C0C8C0C8C0C8C', - symbol: 'USDC', + decimals: 6, }, ], })); @@ -182,7 +182,7 @@ describe('useRelaySell', () => { expect(result.current.isLoading).toBe(false); expect(result.current.error).toBe(null); expect(result.current.isInitialized).toBe(false); - expect(typeof result.current.getUSDCAddress).toBe('function'); + expect(typeof result.current.getUSDCToken).toBe('function'); expect(typeof result.current.getBestSellOffer).toBe('function'); expect(typeof result.current.executeSell).toBe('function'); expect(typeof result.current.buildSellTransactions).toBe('function'); @@ -249,7 +249,7 @@ describe('useRelaySell', () => { expect(result.current.isInitialized).toBe(true); }); - it('getUSDCAddress returns correct address for supported chain', async () => { + it('getUSDCToken returns correct token for supported chain', async () => { const useRelaySdk = await import('../useRelaySdk'); const useTransactionKit = await import( '../../../../hooks/useTransactionKit' @@ -289,11 +289,15 @@ describe('useRelaySell', () => { const { result } = renderHook(() => useRelaySell()); - const usdcAddress = result.current.getUSDCAddress(1); - expect(usdcAddress).toBe('0xA0b86a33E6441b8C4C8C0C8C0C8C0C8C0C8C0C8C'); + const usdcToken = result.current.getUSDCToken(1); + expect(usdcToken?.address).toBe( + '0xA0b86a33E6441b8C4C8C0C8C0C8C0C8C0C8C0C8C' + ); + expect(usdcToken?.decimals).toBe(6); + expect(usdcToken?.chainId).toBe(1); }); - it('getUSDCAddress returns null for unsupported chain', async () => { + it('getUSDCToken returns null for unsupported chain', async () => { const useRelaySdk = await import('../useRelaySdk'); const useTransactionKit = await import( '../../../../hooks/useTransactionKit' @@ -333,8 +337,8 @@ describe('useRelaySell', () => { const { result } = renderHook(() => useRelaySell()); - const usdcAddress = result.current.getUSDCAddress(999); - expect(usdcAddress).toBe(null); + const usdcToken = result.current.getUSDCToken(999); + expect(usdcToken).toBe(null); }); it('getBestSellOffer returns null when not initialized', async () => { @@ -633,7 +637,11 @@ describe('useRelaySell', () => { const { result } = renderHook(() => useRelaySell()); - const success = await result.current.executeSell(mockSelectedToken, '1.0'); + const success = await result.current.executeSell( + mockSelectedToken, + '1.0', + 1 + ); expect(success).toBe(false); diff --git a/src/apps/pulse/hooks/useRelayBuy.ts b/src/apps/pulse/hooks/useRelayBuy.ts index cd9d1ab3..ed5d6c99 100644 --- a/src/apps/pulse/hooks/useRelayBuy.ts +++ b/src/apps/pulse/hooks/useRelayBuy.ts @@ -108,13 +108,15 @@ export default function useRelayBuy() { }, [isInitialized]); /** - * Get the USDC address for a specific chain + * Get the USDC token details for a specific chain */ - const getUSDCAddress = (chainId: number): string | null => { + const getUSDCToken = ( + chainId: number + ): { chainId: number; address: string; decimals: number } | null => { const stableCurrency = STABLE_CURRENCIES.find( (currency) => currency.chainId === chainId ); - return stableCurrency?.address || null; + return stableCurrency ?? null; }; /** @@ -135,12 +137,15 @@ export default function useRelayBuy() { return null; } - const usdcAddress = getUSDCAddress(fromChainId); - if (!usdcAddress) { + const USDCtoken = getUSDCToken(fromChainId); + if (!USDCtoken) { setError('Unable to get quote. Please try again.'); return null; } + const usdcAddress = USDCtoken.address; + const usdcDecimals = USDCtoken.decimals; + setIsLoading(true); setError(null); @@ -172,8 +177,11 @@ export default function useRelayBuy() { // If USDC price is $0.9998, then $10 USD = 10 / 0.9998 = 10.002 USDC const usdcAmount = usdAmount / usdcPrice; - // Convert to wei using 6 decimals (USDC precision) - fromAmountInWei = parseUnits(usdcAmount.toFixed(6), 6); + // Convert to wei using USDC decimals (e.g., 6 on Ethereum, 18 on BSC) + fromAmountInWei = parseUnits( + usdcAmount.toFixed(usdcDecimals), + usdcDecimals + ); } catch (parseError) { console.error('Failed to parse fromAmount:', parseError); setError('Invalid amount. Please try again.'); @@ -308,11 +316,14 @@ export default function useRelayBuy() { } // Get USDC address for the chain - const usdcAddress = getUSDCAddress(fromChainId); - if (!usdcAddress) { + const USDCtoken = getUSDCToken(fromChainId); + if (!USDCtoken) { throw new Error('USDC address not found for chain'); } + const usdcAddress = USDCtoken.address; + const usdcDecimals = USDCtoken.decimals; + // Calculate fee distribution: 1% fee, 99% for swap // Get the USDC amount needed for the swap from the quote const usdcNeededForSwap = BigInt( @@ -357,7 +368,7 @@ export default function useRelayBuy() { t.blockchain === `chain-${fromChainId}` ); if (usdcToken && usdcToken.balance) { - userUsdcBalance = toWei(usdcToken.balance.toString(), 6); // USDC has 6 decimals + userUsdcBalance = toWei(usdcToken.balance.toString(), usdcDecimals); } // Debug: Log balance validation only if there's an issue @@ -788,7 +799,7 @@ export default function useRelayBuy() { ); return { - getUSDCAddress, + getUSDCToken, getBestOffer, executeBuy, buildTransactions, diff --git a/src/apps/pulse/hooks/useRelaySell.ts b/src/apps/pulse/hooks/useRelaySell.ts index f1b1aabe..8d664ed6 100644 --- a/src/apps/pulse/hooks/useRelaySell.ts +++ b/src/apps/pulse/hooks/useRelaySell.ts @@ -112,14 +112,19 @@ export default function useRelaySell() { }, [isInitialized]); /** - * Get the USDC address for a specific chain + * Get the USDC token details for a specific chain */ - const getUSDCAddress = useCallback((chainId: number): string | null => { - const stableCurrency = STABLE_CURRENCIES.find( - (currency) => currency.chainId === chainId - ); - return stableCurrency?.address || null; - }, []); + const getUSDCToken = useCallback( + ( + chainId: number + ): { chainId: number; address: string; decimals: number } | null => { + const stableCurrency = STABLE_CURRENCIES.find( + (currency) => currency.chainId === chainId + ); + return stableCurrency ?? null; + }, + [] + ); /** * Get the best sell offer for swapping a token to USDC @@ -140,12 +145,15 @@ export default function useRelaySell() { return null; } - const usdcAddress = getUSDCAddress(toChainId); - if (!usdcAddress) { + const usdcToken = getUSDCToken(toChainId); + if (!usdcToken) { setError('Unable to get quote. Please try again.'); return null; } + const usdcAddress = usdcToken.address; + const usdcDecimals = usdcToken.decimals; + setIsLoading(true); setError(null); @@ -205,14 +213,15 @@ export default function useRelaySell() { if (currencyOut.amountFormatted) { usdcAmount = parseFloat(currencyOut.amountFormatted); } else if (currencyOut.amount) { - // Convert from raw units to readable format - usdcAmount = parseFloat(currencyOut.amount) / 10 ** 6; // USDC has 6 decimals + // Convert from raw units to readable format using USDC decimals + usdcAmount = parseFloat(currencyOut.amount) / 10 ** usdcDecimals; } // Get the minimum amount (prefer formatted, fallback to raw amount) if (currencyOut.minimumAmount) { - // minimumAmount is in raw units, convert to readable format - minimumAmount = parseFloat(currencyOut.minimumAmount) / 10 ** 6; // USDC has 6 decimals + // minimumAmount is in raw units, convert to readable format using USDC decimals + minimumAmount = + parseFloat(currencyOut.minimumAmount) / 10 ** usdcDecimals; } else { // Fallback: calculate minimum receive using slippage tolerance // Formula: Minimum to receive = Est. to amount × (1 - max.slippage) @@ -329,12 +338,14 @@ export default function useRelaySell() { throw new Error('Fee receiver address is not configured'); } - // Get USDC address for the chain - const usdcAddress = getUSDCAddress(token.chainId); - if (!usdcAddress) { + // Get USDC address and decimals for the chain + const usdcToken = getUSDCToken(token.chainId); + if (!usdcToken) { throw new Error('USDC address not found for chain'); } + const usdcAddress = usdcToken.address; + // Calculate fee distribution: 99% to user, 1% to fee receiver // We swap 100% of the token, then take 1% of the received USDC as fee // Use the guaranteed minimum USDC received from the swap (slippage-protected) @@ -1005,17 +1016,20 @@ export default function useRelaySell() { const usdcAfterFee = usdcAfterFirstSwap * 0.99; // 1% fee taken // Step 3: Get quote for bridging USDC to target chain - const usdcAddress = getUSDCAddress(fromChainId); - if (!usdcAddress) { + const usdcToken = getUSDCToken(fromChainId); + if (!usdcToken) { setError('Unable to get USDC address for source chain'); return null; } + const usdcAddress = usdcToken.address; + const usdcDecimals = usdcToken.decimals; + const bridgeOffer = await getBestSellOffer({ fromAmount: usdcAfterFee.toString(), fromTokenAddress: usdcAddress, fromChainId, - fromTokenDecimals: 6, + fromTokenDecimals: usdcDecimals, toChainId, // Target chain slippage, }); @@ -1065,7 +1079,7 @@ export default function useRelaySell() { setIsLoading(false); } }, - [isInitialized, getBestSellOffer, getUSDCAddress] + [isInitialized, getBestSellOffer, getUSDCToken] ); /** @@ -1133,12 +1147,15 @@ export default function useRelaySell() { const fromAmountBigInt = parseUnits(inputAmount, fromToken.decimals); - // Step 3: Get USDC address - const usdcAddress = getUSDCAddress(fromChainId); - if (!usdcAddress) { + // Step 3: Get USDC address and decimals + const usdcToken = getUSDCToken(fromChainId); + if (!usdcToken) { throw new Error('USDC address not found for source chain'); } + const usdcAddress = usdcToken.address; + const usdcDecimals = usdcToken.decimals; + // Step 4: Calculate fee for first swap (1% of USDC received) const feeReceiver = import.meta.env.VITE_SWAP_FEE_RECEIVER; if (!feeReceiver) { @@ -1399,7 +1416,7 @@ export default function useRelaySell() { // Step 10: Get quote for USDC bridge const usdcToBridgeFormatted = ( Number(usdcAfterFirstSwapFee) / - 10 ** 6 + 10 ** usdcDecimals ).toString(); let bridgeOffer: SellOffer | null = null; @@ -1408,7 +1425,7 @@ export default function useRelaySell() { fromAmount: usdcToBridgeFormatted, fromTokenAddress: usdcAddress, fromChainId, - fromTokenDecimals: 6, + fromTokenDecimals: usdcDecimals, toChainId, // Target chain }); } catch (err) { @@ -1452,7 +1469,10 @@ export default function useRelaySell() { } if (bridgeSpenderAddress) { - const usdcAmountBigInt = parseUnits(usdcToBridgeFormatted, 6); + const usdcAmountBigInt = parseUnits( + usdcToBridgeFormatted, + usdcDecimals + ); const isAllowance = await isAllowanceSet({ owner: walletAddress || '', @@ -1583,7 +1603,7 @@ export default function useRelaySell() { }, [ getBestSellOffer, - getUSDCAddress, + getUSDCToken, isAllowanceSet, walletAddress, transactionDebugLog, @@ -1596,7 +1616,7 @@ export default function useRelaySell() { }, []); return { - getUSDCAddress, + getUSDCToken, getBestSellOffer, getBestSellOfferWithBridge, executeSell, diff --git a/src/apps/pulse/hooks/useTopUp.ts b/src/apps/pulse/hooks/useTopUp.ts index 05b49152..22dbcfa3 100644 --- a/src/apps/pulse/hooks/useTopUp.ts +++ b/src/apps/pulse/hooks/useTopUp.ts @@ -32,11 +32,8 @@ export default function useTopUp() { const [error, setError] = useState(null); const { kit, walletAddress } = useTransactionKit(); - const { - buildSellTransactions, - getUSDCAddress, - isInitialized: isRelayInitialized, - } = useRelaySell(); + const { buildSellTransactions, isInitialized: isRelayInitialized } = + useRelaySell(); const { transactionDebugLog } = useTransactionDebugLogger(); const clearError = useCallback(() => { @@ -60,12 +57,6 @@ export default function useTopUp() { const transactions = []; const { chainId } = selectedToken; - // Get USDC address for the chain - const usdcAddress = getUSDCAddress(chainId); - if (!usdcAddress) { - throw new Error(`USDC not supported on chain ${chainId}`); - } - // Step 1: If non-USDC token, add sell transactions if (sellOffer) { const tokenPrice = parseFloat(selectedToken.usdValue) || 0; @@ -154,7 +145,7 @@ export default function useTopUp() { return transactions; }, - [walletAddress, getUSDCAddress, buildSellTransactions] + [walletAddress, buildSellTransactions] ); /** diff --git a/src/apps/pulse/utils/number.ts b/src/apps/pulse/utils/number.ts index fcaa39fb..6546860a 100644 --- a/src/apps/pulse/utils/number.ts +++ b/src/apps/pulse/utils/number.ts @@ -51,3 +51,18 @@ export function bigIntPow(base: bigint, exponent: bigint): bigint { } return result; } + +export function truncateDecimals( + value: string | number, + decimals: number +): string { + const strValue = typeof value === 'number' ? value.toString() : value; + const [integerPart, decimalPart] = strValue.split('.'); + + if (!decimalPart) { + return integerPart; + } + + const truncatedDecimal = decimalPart.slice(0, decimals); + return `${integerPart}.${truncatedDecimal}`; +} diff --git a/src/services/pillarXApiWalletTransactions.ts b/src/services/pillarXApiWalletTransactions.ts index d3cf796f..efcb2798 100644 --- a/src/services/pillarXApiWalletTransactions.ts +++ b/src/services/pillarXApiWalletTransactions.ts @@ -36,7 +36,7 @@ export const pillarXApiWalletTransactions = createApi({ wallet, limit, page, - filterSpam: true, + filterSpam: false, }, }, }; diff --git a/src/utils/pnl.ts b/src/utils/pnl.ts index 5fb4533e..a3f55c7f 100644 --- a/src/utils/pnl.ts +++ b/src/utils/pnl.ts @@ -18,6 +18,17 @@ const USDC_ADDRESSES = allStableCurrencies.map( currency.address.toLowerCase() ); +/** + * Get the USDC decimals for a specific chain + */ +const getUSDCDecimalsByChainId = (chainId: number): number => { + const usdcToken = allStableCurrencies.find( + (currency: { chainId: number; address: string; decimals: number }) => + currency.chainId === chainId + ) as { chainId: number; address: string; decimals: number } | undefined; + return usdcToken?.decimals ?? 6; // Default to 6 if not found +}; + export const reconstructTrades = ( transactions: MobulaTransactionRow[], walletAddress: string @@ -332,10 +343,12 @@ export const getRelayValidatedTrades = async ( // For BUY, we expect negative USDC (spending) // For SELL, we expect positive USDC (receiving) + const usdcDecimals = getUSDCDecimalsByChainId(token.chainId); + const usdcDivisor = 10 ** usdcDecimals; if (side === 'BUY' && balanceDiff < 0) { - usdcAmount += Math.abs(balanceDiff) / 1e6; // USDC has 6 decimals + usdcAmount += Math.abs(balanceDiff) / usdcDivisor; } else if (side === 'SELL' && balanceDiff > 0) { - usdcAmount += balanceDiff / 1e6; + usdcAmount += balanceDiff / usdcDivisor; } } }); @@ -438,7 +451,8 @@ export const calculatePnLFromRelay = ( // If state changes show token movement, use that if (tokenChange !== 0) { const tokenDivisor = 10 ** token.decimals; - const usdcDivisor = 1e6; + const usdcDecimals = getUSDCDecimalsByChainId(token.chainId); + const usdcDivisor = 10 ** usdcDecimals; const tokenAmountRaw = Math.abs(tokenChange) / tokenDivisor; const usdcAmountRaw = Math.abs(usdcChange) / usdcDivisor;