Skip to content
11 changes: 7 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion src/apps/pulse/components/Buy/PayingToken.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -85,7 +86,7 @@ export default function PayingToken(props: PayingTokenProps) {
</div>
<div className="flex flex-col justify-center text-right">
<div className="text-[13px] font-normal text-white">
{payingToken.totalRaw}
{truncateDecimals(payingToken.totalRaw, 6)}
</div>
<div className="text-xs font-normal text-white/50">
${payingToken.totalUsd.toFixed(2)}
Expand Down
5 changes: 5 additions & 0 deletions src/apps/pulse/components/Buy/tests/Buy.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions src/apps/pulse/components/Sell/PreviewSell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const PreviewSell = (props: PreviewSellProps) => {
const [isTransactionSuccess, setIsTransactionSuccess] = useState(false);
const previewModalRef = useRef<HTMLDivElement>(null);
const {
getUSDCAddress,
getUSDCToken,
executeSell,
error,
clearError,
Expand Down Expand Up @@ -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(() => {
Expand Down
20 changes: 16 additions & 4 deletions src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ describe('<PreviewSell />', () => {
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(),
Expand Down Expand Up @@ -259,7 +263,11 @@ describe('<PreviewSell />', () => {
})
);
(useRelaySell as any).mockReturnValue({
getUSDCAddress: vi.fn(() => '0xUSDC1234567890'),
getUSDCToken: vi.fn(() => ({
chainId: 1,
address: '0xUSDC1234567890',
decimals: 6,
})),
executeSell: mockExecuteSell,
error: null,
clearError: vi.fn(),
Expand Down Expand Up @@ -299,7 +307,11 @@ describe('<PreviewSell />', () => {
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(),
Expand Down Expand Up @@ -334,7 +346,7 @@ describe('<PreviewSell />', () => {
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(),
Expand Down
42 changes: 35 additions & 7 deletions src/apps/pulse/constants/tokens.ts
Original file line number Diff line number Diff line change
@@ -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(
Expand Down
26 changes: 17 additions & 9 deletions src/apps/pulse/hooks/tests/useRelaySell.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ vi.mock('../../constants/tokens', () => ({
{
chainId: 1,
address: '0xA0b86a33E6441b8C4C8C0C8C0C8C0C8C0C8C0C8C',
symbol: 'USDC',
decimals: 6,
},
],
}));
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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);

Expand Down
33 changes: 22 additions & 11 deletions src/apps/pulse/hooks/useRelayBuy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

/**
Expand All @@ -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);

Expand Down Expand Up @@ -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.');
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -788,7 +799,7 @@ export default function useRelayBuy() {
);

return {
getUSDCAddress,
getUSDCToken,
getBestOffer,
executeBuy,
buildTransactions,
Expand Down
Loading