From ba1d3b152837d9929e6f2e8b3812262cc2785637 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Wed, 20 Nov 2024 13:37:21 -0800 Subject: [PATCH 01/16] add useFundSwapTokens utility --- src/swap/constants.ts | 23 +++++ src/swap/hooks/useFundSwapTokens.test.ts | 116 +++++++++++++++++++++++ src/swap/hooks/useFundSwapTokens.ts | 78 +++++++++++++++ src/swap/types.ts | 8 ++ 4 files changed, 225 insertions(+) create mode 100644 src/swap/hooks/useFundSwapTokens.test.ts create mode 100644 src/swap/hooks/useFundSwapTokens.ts diff --git a/src/swap/constants.ts b/src/swap/constants.ts index ad222a5704..d2a04e933e 100644 --- a/src/swap/constants.ts +++ b/src/swap/constants.ts @@ -1,3 +1,6 @@ +import type { Token } from '../token'; +import { base } from 'viem/chains'; + export const FALLBACK_DEFAULT_MAX_SLIPPAGE = 3; export const GENERAL_SWAP_ERROR_CODE = 'SWAP_ERROR'; export const GENERAL_SWAP_QUOTE_ERROR_CODE = 'SWAP_QUOTE_ERROR'; @@ -23,3 +26,23 @@ export enum SwapMessage { TOO_MANY_REQUESTS = 'Too many requests. Please try again later.', USER_REJECTED = 'User rejected the transaction', } + +export const ethToken: Token = { + name: 'ETH', + address: '', + symbol: 'ETH', + decimals: 18, + image: + 'https://wallet-api-production.s3.amazonaws.com/uploads/tokens/eth_288.png', + chainId: base.id, +}; + +export const usdcToken: Token = { + name: 'USDC', + address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', + symbol: 'USDC', + decimals: 6, + image: + 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/44/2b/442b80bd16af0c0d9b22e03a16753823fe826e5bfd457292b55fa0ba8c1ba213-ZWUzYjJmZGUtMDYxNy00NDcyLTg0NjQtMWI4OGEwYjBiODE2', + chainId: base.id, +}; diff --git a/src/swap/hooks/useFundSwapTokens.test.ts b/src/swap/hooks/useFundSwapTokens.test.ts new file mode 100644 index 0000000000..83b5cb8b19 --- /dev/null +++ b/src/swap/hooks/useFundSwapTokens.test.ts @@ -0,0 +1,116 @@ +import { act, renderHook } from '@testing-library/react'; +import { base } from 'viem/chains'; +import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; +import { useValue } from '../../internal/hooks/useValue'; +import type { Token } from '../../token'; +import { USDC_TOKEN } from '../mocks'; +import { useFundSwapTokens } from './useFundSwapTokens'; +import { useSwapBalances } from './useSwapBalances'; + +vi.mock('./useSwapBalances', () => ({ + useSwapBalances: vi.fn(), +})); + +vi.mock('../../internal/hooks/useValue', () => ({ + useValue: vi.fn(), +})); + +const toToken: Token = { + name: 'DEGEN', + address: '0x4ed4e862860bed51a9570b96d89af5e1b0efefed', + symbol: 'DEGEN', + decimals: 18, + image: + 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/3b/bf/3bbf118b5e6dc2f9e7fc607a6e7526647b4ba8f0bea87125f971446d57b296d2-MDNmNjY0MmEtNGFiZi00N2I0LWIwMTItMDUyMzg2ZDZhMWNm', + chainId: base.id, +}; + +describe('useFundSwapTokens', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should return correct values', () => { + (useSwapBalances as Mock).mockReturnValue({ + fromBalanceString: '100', + fromTokenBalanceError: null, + fromTokenResponse: { refetch: vi.fn() }, + toBalanceString: '200', + toTokenBalanceError: null, + toTokenResponse: { refetch: vi.fn() }, + }); + (useValue as Mock).mockImplementation((props) => ({ + ...props, + amount: '100', + amountUSD: '150', + response: props.response, + setAmount: vi.fn(), + setAmountUSD: vi.fn(), + setLoading: vi.fn(), + token: USDC_TOKEN, + })); + const { result } = renderHook(() => useFundSwapTokens(toToken, '0x123')); + expect(result.current.fromETH).toEqual({ + amount: '100', + amountUSD: '150', + balance: '100', + balanceResponse: { refetch: expect.any(Function) }, + error: null, + loading: false, + setAmount: expect.any(Function), + setAmountUSD: expect.any(Function), + setLoading: expect.any(Function), + token: USDC_TOKEN, + }); + expect(result.current.to).toEqual({ + amount: '100', + amountUSD: '150', + balance: '200', + balanceResponse: { refetch: expect.any(Function) }, + error: null, + loading: false, + setAmount: expect.any(Function), + setAmountUSD: expect.any(Function), + setLoading: expect.any(Function), + token: USDC_TOKEN, + }); + }); + + it('should call fromTokenResponse.refetch when fromETH.response.refetch is called', async () => { + const mockFromRefetch = vi.fn().mockResolvedValue(undefined); + const mockToRefetch = vi.fn().mockResolvedValue(undefined); + (useSwapBalances as Mock).mockReturnValue({ + fromTokenResponse: { refetch: mockFromRefetch }, + toTokenResponse: { refetch: mockToRefetch }, + }); + (useValue as Mock).mockImplementation((props) => ({ + ...props, + response: props.response, + })); + const { result } = renderHook(() => useFundSwapTokens(toToken, '0x123')); + await act(async () => { + await result.current.fromETH.balanceResponse?.refetch(); + }); + expect(mockFromRefetch).toHaveBeenCalledTimes(1); + expect(mockToRefetch).not.toHaveBeenCalled(); + }); + + it('should call toTokenResponse.refetch when to.response.refetch is called', async () => { + const mockFromRefetch = vi.fn().mockResolvedValue(undefined); + const mockToRefetch = vi.fn().mockResolvedValue(undefined); + (useSwapBalances as Mock).mockReturnValue({ + fromTokenResponse: { refetch: mockFromRefetch }, + toTokenResponse: { refetch: mockToRefetch }, + }); + (useValue as Mock).mockImplementation((props) => ({ + ...props, + response: props.response, + })); + const { result } = renderHook(() => useFundSwapTokens(toToken, '0x123')); + await act(async () => { + await result.current.to.balanceResponse?.refetch(); + }); + expect(mockToRefetch).toHaveBeenCalledTimes(1); + expect(mockFromRefetch).not.toHaveBeenCalled(); + }); +}); diff --git a/src/swap/hooks/useFundSwapTokens.ts b/src/swap/hooks/useFundSwapTokens.ts new file mode 100644 index 0000000000..547f934cfb --- /dev/null +++ b/src/swap/hooks/useFundSwapTokens.ts @@ -0,0 +1,78 @@ +import { useState } from 'react'; +import type { Address } from 'viem'; +import { ethToken, usdcToken } from '../constants'; +import { useValue } from '../../internal/hooks/useValue'; +import type { Token } from '../../token'; +import type { FundSwapTokens } from '../types'; +import { useSwapBalances } from './useSwapBalances'; + +export const useFundSwapTokens = ( + toToken: Token, + address?: Address, +): FundSwapTokens => { + const [toAmount, setToAmount] = useState(''); + const [toAmountUSD, setToAmountUSD] = useState(''); + const [toLoading, setToLoading] = useState(false); + const [fromETHAmount, setFromETHAmount] = useState(''); + const [fromETHAmountUSD, setFromETHAmountUSD] = useState(''); + const [fromETHLoading, setFromETHLoading] = useState(false); + const [fromUSDCAmount, setFromUSDCAmount] = useState(''); + const [fromUSDCAmountUSD, setFromUSDCAmountUSD] = useState(''); + const [fromUSDCLoading, setFromUSDCLoading] = useState(false); + + const { + fromBalanceString: fromETHBalanceString, + fromTokenBalanceError: fromEthBalanceError, + toBalanceString, + toTokenBalanceError, + fromTokenResponse: fromETHResponse, + toTokenResponse, + } = useSwapBalances({ address, fromToken: ethToken, toToken }); + + const { + fromBalanceString: fromUSDCBalanceString, + fromTokenBalanceError: fromUSDCBalanceError, + fromTokenResponse: fromUSDCResponse, + } = useSwapBalances({ address, fromToken: usdcToken, toToken }); + + const fromETH = useValue({ + balance: fromETHBalanceString, + balanceResponse: fromETHResponse, + amount: fromETHAmount, + setAmount: setFromETHAmount, + amountUSD: fromETHAmountUSD, + setAmountUSD: setFromETHAmountUSD, + token: ethToken, + loading: fromETHLoading, + setLoading: setFromETHLoading, + error: fromEthBalanceError, + }); + + const fromUSDC = useValue({ + balance: fromUSDCBalanceString, + balanceResponse: fromUSDCResponse, + amount: fromUSDCAmount, + setAmount: setFromUSDCAmount, + amountUSD: fromUSDCAmountUSD, + setAmountUSD: setFromUSDCAmountUSD, + token: usdcToken, + loading: fromUSDCLoading, + setLoading: setFromUSDCLoading, + error: fromUSDCBalanceError, + }); + + const to = useValue({ + balance: toBalanceString, + balanceResponse: toTokenResponse, + amount: toAmount, + amountUSD: toAmountUSD, + setAmountUSD: setToAmountUSD, + setAmount: setToAmount, + token: toToken, + loading: toLoading, + setLoading: setToLoading, + error: toTokenBalanceError, + }); + + return { fromETH, fromUSDC, to }; +}; diff --git a/src/swap/types.ts b/src/swap/types.ts index de617e9994..32fca9c145 100644 --- a/src/swap/types.ts +++ b/src/swap/types.ts @@ -54,6 +54,14 @@ export type FromTo = { to: SwapUnit; }; +export type FundSwapTokens = { + fromETH: FundSwapUnit; + fromUSDC: FundSwapUnit; + to: FundSwapUnit; +}; + +export type FundSwapUnit = Omit; + export type GetSwapMessageParams = { address?: Address; lifecycleStatus: LifecycleStatus; From 3bfa89cf3b838d740751cccd029398dff15726f7 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Wed, 20 Nov 2024 13:48:13 -0800 Subject: [PATCH 02/16] add useResetFundSwapInputs utility --- src/swap/hooks/useResetFundSwapInputs.test.ts | 150 ++++++++++++++++++ src/swap/hooks/useResetFundSwapInputs.ts | 23 +++ 2 files changed, 173 insertions(+) create mode 100644 src/swap/hooks/useResetFundSwapInputs.test.ts create mode 100644 src/swap/hooks/useResetFundSwapInputs.ts diff --git a/src/swap/hooks/useResetFundSwapInputs.test.ts b/src/swap/hooks/useResetFundSwapInputs.test.ts new file mode 100644 index 0000000000..2fc25bccb3 --- /dev/null +++ b/src/swap/hooks/useResetFundSwapInputs.test.ts @@ -0,0 +1,150 @@ +import { act, renderHook } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { FundSwapUnit } from '../types'; +import { useResetFundSwapInputs } from './useResetFundSwapInputs'; + +describe('useResetFundSwapInputs', () => { + const mockFromTokenResponse = { + refetch: vi.fn().mockResolvedValue(undefined), + }; + const mockFromUSDCTokenResponse = { + refetch: vi.fn().mockResolvedValue(undefined), + }; + const mockToTokenResponse = { refetch: vi.fn().mockResolvedValue(undefined) }; + const mockFromETH: FundSwapUnit = { + balance: '100', + balanceResponse: mockFromTokenResponse, + amount: '50', + setAmount: vi.fn(), + setAmountUSD: vi.fn(), + token: undefined, + loading: false, + setLoading: vi.fn(), + error: undefined, + }; + const mockFromUSDC: FundSwapUnit = { + balance: '100', + balanceResponse: mockFromUSDCTokenResponse, + amount: '50', + setAmount: vi.fn(), + setAmountUSD: vi.fn(), + token: undefined, + loading: false, + setLoading: vi.fn(), + error: undefined, + }; + const mockTo: FundSwapUnit = { + balance: '200', + balanceResponse: mockToTokenResponse, + amount: '75', + setAmount: vi.fn(), + setAmountUSD: vi.fn(), + token: undefined, + loading: false, + setLoading: vi.fn(), + error: undefined, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should return a function', () => { + const { result } = renderHook(() => + useResetFundSwapInputs({ + fromETH: mockFromETH, + fromUSDC: mockFromUSDC, + to: mockTo, + }), + ); + expect(typeof result.current).toBe('function'); + }); + + it('should call refetch on responses and set amounts to empty strings when executed', async () => { + const { result } = renderHook(() => + useResetFundSwapInputs({ + fromETH: mockFromETH, + fromUSDC: mockFromUSDC, + to: mockTo, + }), + ); + await act(async () => { + await result.current(); + }); + expect(mockFromTokenResponse.refetch).toHaveBeenCalledTimes(1); + expect(mockToTokenResponse.refetch).toHaveBeenCalledTimes(1); + expect(mockFromETH.setAmount).toHaveBeenCalledWith(''); + expect(mockFromETH.setAmountUSD).toHaveBeenCalledWith(''); + expect(mockTo.setAmount).toHaveBeenCalledWith(''); + expect(mockTo.setAmountUSD).toHaveBeenCalledWith(''); + }); + + it("should not create a new function reference if from and to haven't changed", () => { + const { result, rerender } = renderHook(() => + useResetFundSwapInputs({ + fromETH: mockFromETH, + fromUSDC: mockFromUSDC, + to: mockTo, + }), + ); + const firstRender = result.current; + rerender(); + expect(result.current).toBe(firstRender); + }); + + it('should create a new function reference if from or to change', () => { + const { result, rerender } = renderHook( + ({ fromETH, fromUSDC, to }) => + useResetFundSwapInputs({ + fromETH, + fromUSDC, + to, + }), + { + initialProps: { + fromETH: mockFromETH, + fromUSDC: mockFromUSDC, + to: mockTo, + }, + }, + ); + const firstRender = result.current; + const newMockFromETH = { + ...mockFromETH, + balanceResponse: { refetch: vi.fn().mockResolvedValue(undefined) }, + }; + const newMockFromUSDC = { + ...mockFromUSDC, + balanceResponse: { refetch: vi.fn().mockResolvedValue(undefined) }, + }; + rerender({ + fromETH: newMockFromETH, + fromUSDC: newMockFromUSDC, + to: mockTo, + }); + expect(result.current).not.toBe(firstRender); + }); + + it('should handle null responses gracefully', async () => { + const mockFromWithNullResponse = { ...mockFromETH, balanceResponse: null }; + const mockFromUSDCWithNullResponse = { + ...mockFromUSDC, + balanceResponse: null, + }; + const mockToWithNullResponse = { ...mockTo, balanceResponse: null }; + const { result } = renderHook(() => + useResetFundSwapInputs({ + fromETH: mockFromWithNullResponse, + fromUSDC: mockFromUSDCWithNullResponse, + to: mockToWithNullResponse, + }), + ); + await act(async () => { + await result.current(); + }); + expect(mockFromWithNullResponse.setAmount).toHaveBeenCalledWith(''); + expect(mockFromWithNullResponse.setAmountUSD).toHaveBeenCalledWith(''); + expect(mockToWithNullResponse.setAmount).toHaveBeenCalledWith(''); + expect(mockToWithNullResponse.setAmountUSD).toHaveBeenCalledWith(''); + }); +}); diff --git a/src/swap/hooks/useResetFundSwapInputs.ts b/src/swap/hooks/useResetFundSwapInputs.ts new file mode 100644 index 0000000000..26f7af2935 --- /dev/null +++ b/src/swap/hooks/useResetFundSwapInputs.ts @@ -0,0 +1,23 @@ +import { useCallback } from 'react'; +import type { FundSwapTokens } from '../types'; + +// Refreshes balances and inputs post-swap +export const useResetFundSwapInputs = ({ + fromETH, + fromUSDC, + to, +}: FundSwapTokens) => { + return useCallback(async () => { + await Promise.all([ + fromETH.balanceResponse?.refetch(), + fromUSDC.balanceResponse?.refetch(), + to.balanceResponse?.refetch(), + fromETH.setAmount(''), + fromUSDC.setAmount(''), + fromETH.setAmountUSD(''), + fromUSDC.setAmountUSD(''), + to.setAmount(''), + to.setAmountUSD(''), + ]); + }, [fromETH, fromUSDC, to]); +}; From 15f0da5f919fa94b48a3c3d143b2b154fad14454 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Fri, 13 Dec 2024 11:34:07 -0800 Subject: [PATCH 03/16] remove previous functions --- src/swap/hooks/useFundSwapTokens.test.ts | 116 -------------- src/swap/hooks/useFundSwapTokens.ts | 78 --------- src/swap/hooks/useResetFundSwapInputs.test.ts | 150 ------------------ src/swap/hooks/useResetFundSwapInputs.ts | 23 --- 4 files changed, 367 deletions(-) delete mode 100644 src/swap/hooks/useFundSwapTokens.test.ts delete mode 100644 src/swap/hooks/useFundSwapTokens.ts delete mode 100644 src/swap/hooks/useResetFundSwapInputs.test.ts delete mode 100644 src/swap/hooks/useResetFundSwapInputs.ts diff --git a/src/swap/hooks/useFundSwapTokens.test.ts b/src/swap/hooks/useFundSwapTokens.test.ts deleted file mode 100644 index 83b5cb8b19..0000000000 --- a/src/swap/hooks/useFundSwapTokens.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { act, renderHook } from '@testing-library/react'; -import { base } from 'viem/chains'; -import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; -import { useValue } from '../../internal/hooks/useValue'; -import type { Token } from '../../token'; -import { USDC_TOKEN } from '../mocks'; -import { useFundSwapTokens } from './useFundSwapTokens'; -import { useSwapBalances } from './useSwapBalances'; - -vi.mock('./useSwapBalances', () => ({ - useSwapBalances: vi.fn(), -})); - -vi.mock('../../internal/hooks/useValue', () => ({ - useValue: vi.fn(), -})); - -const toToken: Token = { - name: 'DEGEN', - address: '0x4ed4e862860bed51a9570b96d89af5e1b0efefed', - symbol: 'DEGEN', - decimals: 18, - image: - 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/3b/bf/3bbf118b5e6dc2f9e7fc607a6e7526647b4ba8f0bea87125f971446d57b296d2-MDNmNjY0MmEtNGFiZi00N2I0LWIwMTItMDUyMzg2ZDZhMWNm', - chainId: base.id, -}; - -describe('useFundSwapTokens', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('should return correct values', () => { - (useSwapBalances as Mock).mockReturnValue({ - fromBalanceString: '100', - fromTokenBalanceError: null, - fromTokenResponse: { refetch: vi.fn() }, - toBalanceString: '200', - toTokenBalanceError: null, - toTokenResponse: { refetch: vi.fn() }, - }); - (useValue as Mock).mockImplementation((props) => ({ - ...props, - amount: '100', - amountUSD: '150', - response: props.response, - setAmount: vi.fn(), - setAmountUSD: vi.fn(), - setLoading: vi.fn(), - token: USDC_TOKEN, - })); - const { result } = renderHook(() => useFundSwapTokens(toToken, '0x123')); - expect(result.current.fromETH).toEqual({ - amount: '100', - amountUSD: '150', - balance: '100', - balanceResponse: { refetch: expect.any(Function) }, - error: null, - loading: false, - setAmount: expect.any(Function), - setAmountUSD: expect.any(Function), - setLoading: expect.any(Function), - token: USDC_TOKEN, - }); - expect(result.current.to).toEqual({ - amount: '100', - amountUSD: '150', - balance: '200', - balanceResponse: { refetch: expect.any(Function) }, - error: null, - loading: false, - setAmount: expect.any(Function), - setAmountUSD: expect.any(Function), - setLoading: expect.any(Function), - token: USDC_TOKEN, - }); - }); - - it('should call fromTokenResponse.refetch when fromETH.response.refetch is called', async () => { - const mockFromRefetch = vi.fn().mockResolvedValue(undefined); - const mockToRefetch = vi.fn().mockResolvedValue(undefined); - (useSwapBalances as Mock).mockReturnValue({ - fromTokenResponse: { refetch: mockFromRefetch }, - toTokenResponse: { refetch: mockToRefetch }, - }); - (useValue as Mock).mockImplementation((props) => ({ - ...props, - response: props.response, - })); - const { result } = renderHook(() => useFundSwapTokens(toToken, '0x123')); - await act(async () => { - await result.current.fromETH.balanceResponse?.refetch(); - }); - expect(mockFromRefetch).toHaveBeenCalledTimes(1); - expect(mockToRefetch).not.toHaveBeenCalled(); - }); - - it('should call toTokenResponse.refetch when to.response.refetch is called', async () => { - const mockFromRefetch = vi.fn().mockResolvedValue(undefined); - const mockToRefetch = vi.fn().mockResolvedValue(undefined); - (useSwapBalances as Mock).mockReturnValue({ - fromTokenResponse: { refetch: mockFromRefetch }, - toTokenResponse: { refetch: mockToRefetch }, - }); - (useValue as Mock).mockImplementation((props) => ({ - ...props, - response: props.response, - })); - const { result } = renderHook(() => useFundSwapTokens(toToken, '0x123')); - await act(async () => { - await result.current.to.balanceResponse?.refetch(); - }); - expect(mockToRefetch).toHaveBeenCalledTimes(1); - expect(mockFromRefetch).not.toHaveBeenCalled(); - }); -}); diff --git a/src/swap/hooks/useFundSwapTokens.ts b/src/swap/hooks/useFundSwapTokens.ts deleted file mode 100644 index 547f934cfb..0000000000 --- a/src/swap/hooks/useFundSwapTokens.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { useState } from 'react'; -import type { Address } from 'viem'; -import { ethToken, usdcToken } from '../constants'; -import { useValue } from '../../internal/hooks/useValue'; -import type { Token } from '../../token'; -import type { FundSwapTokens } from '../types'; -import { useSwapBalances } from './useSwapBalances'; - -export const useFundSwapTokens = ( - toToken: Token, - address?: Address, -): FundSwapTokens => { - const [toAmount, setToAmount] = useState(''); - const [toAmountUSD, setToAmountUSD] = useState(''); - const [toLoading, setToLoading] = useState(false); - const [fromETHAmount, setFromETHAmount] = useState(''); - const [fromETHAmountUSD, setFromETHAmountUSD] = useState(''); - const [fromETHLoading, setFromETHLoading] = useState(false); - const [fromUSDCAmount, setFromUSDCAmount] = useState(''); - const [fromUSDCAmountUSD, setFromUSDCAmountUSD] = useState(''); - const [fromUSDCLoading, setFromUSDCLoading] = useState(false); - - const { - fromBalanceString: fromETHBalanceString, - fromTokenBalanceError: fromEthBalanceError, - toBalanceString, - toTokenBalanceError, - fromTokenResponse: fromETHResponse, - toTokenResponse, - } = useSwapBalances({ address, fromToken: ethToken, toToken }); - - const { - fromBalanceString: fromUSDCBalanceString, - fromTokenBalanceError: fromUSDCBalanceError, - fromTokenResponse: fromUSDCResponse, - } = useSwapBalances({ address, fromToken: usdcToken, toToken }); - - const fromETH = useValue({ - balance: fromETHBalanceString, - balanceResponse: fromETHResponse, - amount: fromETHAmount, - setAmount: setFromETHAmount, - amountUSD: fromETHAmountUSD, - setAmountUSD: setFromETHAmountUSD, - token: ethToken, - loading: fromETHLoading, - setLoading: setFromETHLoading, - error: fromEthBalanceError, - }); - - const fromUSDC = useValue({ - balance: fromUSDCBalanceString, - balanceResponse: fromUSDCResponse, - amount: fromUSDCAmount, - setAmount: setFromUSDCAmount, - amountUSD: fromUSDCAmountUSD, - setAmountUSD: setFromUSDCAmountUSD, - token: usdcToken, - loading: fromUSDCLoading, - setLoading: setFromUSDCLoading, - error: fromUSDCBalanceError, - }); - - const to = useValue({ - balance: toBalanceString, - balanceResponse: toTokenResponse, - amount: toAmount, - amountUSD: toAmountUSD, - setAmountUSD: setToAmountUSD, - setAmount: setToAmount, - token: toToken, - loading: toLoading, - setLoading: setToLoading, - error: toTokenBalanceError, - }); - - return { fromETH, fromUSDC, to }; -}; diff --git a/src/swap/hooks/useResetFundSwapInputs.test.ts b/src/swap/hooks/useResetFundSwapInputs.test.ts deleted file mode 100644 index 2fc25bccb3..0000000000 --- a/src/swap/hooks/useResetFundSwapInputs.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { act, renderHook } from '@testing-library/react'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import type { FundSwapUnit } from '../types'; -import { useResetFundSwapInputs } from './useResetFundSwapInputs'; - -describe('useResetFundSwapInputs', () => { - const mockFromTokenResponse = { - refetch: vi.fn().mockResolvedValue(undefined), - }; - const mockFromUSDCTokenResponse = { - refetch: vi.fn().mockResolvedValue(undefined), - }; - const mockToTokenResponse = { refetch: vi.fn().mockResolvedValue(undefined) }; - const mockFromETH: FundSwapUnit = { - balance: '100', - balanceResponse: mockFromTokenResponse, - amount: '50', - setAmount: vi.fn(), - setAmountUSD: vi.fn(), - token: undefined, - loading: false, - setLoading: vi.fn(), - error: undefined, - }; - const mockFromUSDC: FundSwapUnit = { - balance: '100', - balanceResponse: mockFromUSDCTokenResponse, - amount: '50', - setAmount: vi.fn(), - setAmountUSD: vi.fn(), - token: undefined, - loading: false, - setLoading: vi.fn(), - error: undefined, - }; - const mockTo: FundSwapUnit = { - balance: '200', - balanceResponse: mockToTokenResponse, - amount: '75', - setAmount: vi.fn(), - setAmountUSD: vi.fn(), - token: undefined, - loading: false, - setLoading: vi.fn(), - error: undefined, - }; - - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('should return a function', () => { - const { result } = renderHook(() => - useResetFundSwapInputs({ - fromETH: mockFromETH, - fromUSDC: mockFromUSDC, - to: mockTo, - }), - ); - expect(typeof result.current).toBe('function'); - }); - - it('should call refetch on responses and set amounts to empty strings when executed', async () => { - const { result } = renderHook(() => - useResetFundSwapInputs({ - fromETH: mockFromETH, - fromUSDC: mockFromUSDC, - to: mockTo, - }), - ); - await act(async () => { - await result.current(); - }); - expect(mockFromTokenResponse.refetch).toHaveBeenCalledTimes(1); - expect(mockToTokenResponse.refetch).toHaveBeenCalledTimes(1); - expect(mockFromETH.setAmount).toHaveBeenCalledWith(''); - expect(mockFromETH.setAmountUSD).toHaveBeenCalledWith(''); - expect(mockTo.setAmount).toHaveBeenCalledWith(''); - expect(mockTo.setAmountUSD).toHaveBeenCalledWith(''); - }); - - it("should not create a new function reference if from and to haven't changed", () => { - const { result, rerender } = renderHook(() => - useResetFundSwapInputs({ - fromETH: mockFromETH, - fromUSDC: mockFromUSDC, - to: mockTo, - }), - ); - const firstRender = result.current; - rerender(); - expect(result.current).toBe(firstRender); - }); - - it('should create a new function reference if from or to change', () => { - const { result, rerender } = renderHook( - ({ fromETH, fromUSDC, to }) => - useResetFundSwapInputs({ - fromETH, - fromUSDC, - to, - }), - { - initialProps: { - fromETH: mockFromETH, - fromUSDC: mockFromUSDC, - to: mockTo, - }, - }, - ); - const firstRender = result.current; - const newMockFromETH = { - ...mockFromETH, - balanceResponse: { refetch: vi.fn().mockResolvedValue(undefined) }, - }; - const newMockFromUSDC = { - ...mockFromUSDC, - balanceResponse: { refetch: vi.fn().mockResolvedValue(undefined) }, - }; - rerender({ - fromETH: newMockFromETH, - fromUSDC: newMockFromUSDC, - to: mockTo, - }); - expect(result.current).not.toBe(firstRender); - }); - - it('should handle null responses gracefully', async () => { - const mockFromWithNullResponse = { ...mockFromETH, balanceResponse: null }; - const mockFromUSDCWithNullResponse = { - ...mockFromUSDC, - balanceResponse: null, - }; - const mockToWithNullResponse = { ...mockTo, balanceResponse: null }; - const { result } = renderHook(() => - useResetFundSwapInputs({ - fromETH: mockFromWithNullResponse, - fromUSDC: mockFromUSDCWithNullResponse, - to: mockToWithNullResponse, - }), - ); - await act(async () => { - await result.current(); - }); - expect(mockFromWithNullResponse.setAmount).toHaveBeenCalledWith(''); - expect(mockFromWithNullResponse.setAmountUSD).toHaveBeenCalledWith(''); - expect(mockToWithNullResponse.setAmount).toHaveBeenCalledWith(''); - expect(mockToWithNullResponse.setAmountUSD).toHaveBeenCalledWith(''); - }); -}); diff --git a/src/swap/hooks/useResetFundSwapInputs.ts b/src/swap/hooks/useResetFundSwapInputs.ts deleted file mode 100644 index 26f7af2935..0000000000 --- a/src/swap/hooks/useResetFundSwapInputs.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useCallback } from 'react'; -import type { FundSwapTokens } from '../types'; - -// Refreshes balances and inputs post-swap -export const useResetFundSwapInputs = ({ - fromETH, - fromUSDC, - to, -}: FundSwapTokens) => { - return useCallback(async () => { - await Promise.all([ - fromETH.balanceResponse?.refetch(), - fromUSDC.balanceResponse?.refetch(), - to.balanceResponse?.refetch(), - fromETH.setAmount(''), - fromUSDC.setAmount(''), - fromETH.setAmountUSD(''), - fromUSDC.setAmountUSD(''), - to.setAmount(''), - to.setAmountUSD(''), - ]); - }, [fromETH, fromUSDC, to]); -}; From 3002cc441c204b025b1f14dadfc9f986ef812ed4 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Fri, 13 Dec 2024 11:35:14 -0800 Subject: [PATCH 04/16] add new uti functions --- src/core/api/getSwapLiteQuote.ts | 68 +++++++ src/swap/components/SwapProvider.test.tsx | 12 +- src/swap/components/SwapProvider.tsx | 4 +- src/swap/hooks/useResetSwapLiteInputs.test.ts | 166 ++++++++++++++++++ src/swap/hooks/useResetSwapLiteInputs.ts | 27 +++ src/swap/hooks/useSwapLiteTokens.test.ts | 132 ++++++++++++++ src/swap/hooks/useSwapLiteTokens.ts | 104 +++++++++++ src/swap/types.ts | 17 +- 8 files changed, 513 insertions(+), 17 deletions(-) create mode 100644 src/core/api/getSwapLiteQuote.ts create mode 100644 src/swap/hooks/useResetSwapLiteInputs.test.ts create mode 100644 src/swap/hooks/useResetSwapLiteInputs.ts create mode 100644 src/swap/hooks/useSwapLiteTokens.test.ts create mode 100644 src/swap/hooks/useSwapLiteTokens.ts diff --git a/src/core/api/getSwapLiteQuote.ts b/src/core/api/getSwapLiteQuote.ts new file mode 100644 index 0000000000..6109bd8a59 --- /dev/null +++ b/src/core/api/getSwapLiteQuote.ts @@ -0,0 +1,68 @@ +import type { SwapError, SwapUnit } from '../../swap/types'; +import { isSwapError } from '../../swap/utils/isSwapError'; +import type { Token } from '../../token'; +import { formatTokenAmount } from '../utils/formatTokenAmount'; +import { getSwapQuote } from './getSwapQuote'; +import type { + APIError, + GetSwapQuoteParams, + GetSwapQuoteResponse, +} from './types'; + +type GetSwapLiteQuoteResponse = { + response?: GetSwapQuoteResponse; + error?: APIError; + formattedFromAmount?: string; +}; + +type GetSwapLiteQuoteParams = Omit & { + fromSwapUnit: SwapUnit; + from?: Token; +}; + +export async function getSwapLiteQuote({ + amount, + amountReference, + from, + maxSlippage, + to, + useAggregator, + fromSwapUnit, +}: GetSwapLiteQuoteParams): Promise { + // only fetch quote if the from token is provided + if (!from) { + return { response: undefined, formattedFromAmount: '', error: undefined }; + } + + let response: GetSwapQuoteResponse | undefined; + // only fetch quote if the from and to tokens are different + if (to?.symbol !== from?.symbol) { + response = await getSwapQuote({ + amount, + amountReference, + from, + maxSlippage, + to, + useAggregator, + }); + } + + let formattedFromAmount = ''; + if (response && !isSwapError(response)) { + formattedFromAmount = formatTokenAmount( + response.fromAmount, + response.from.decimals, + ); + + fromSwapUnit.setAmountUSD(response?.fromAmountUSD || ''); + fromSwapUnit.setAmount(formattedFromAmount || ''); + } + + let error: SwapError | undefined; + if (isSwapError(response)) { + error = response; + response = undefined; + } + + return { response, formattedFromAmount, error }; +} diff --git a/src/swap/components/SwapProvider.test.tsx b/src/swap/components/SwapProvider.test.tsx index 8fcfffda7b..c2528b1b70 100644 --- a/src/swap/components/SwapProvider.test.tsx +++ b/src/swap/components/SwapProvider.test.tsx @@ -144,9 +144,9 @@ const renderWithProviders = ({ const TestSwapComponent = () => { const context = useSwapContext(); useEffect(() => { - context.from.setToken(ETH_TOKEN); + context.from.setToken?.(ETH_TOKEN); context.from.setAmount('100'); - context.to.setToken(DEGEN_TOKEN); + context.to.setToken?.(DEGEN_TOKEN); }, [context]); const handleStatusError = async () => { context.updateLifecycleStatus({ @@ -555,8 +555,8 @@ describe('SwapProvider', () => { React.useEffect(() => { const initializeSwap = async () => { await act(async () => { - from.setToken(ETH_TOKEN); - to.setToken(DEGEN_TOKEN); + from.setToken?.(ETH_TOKEN); + to.setToken?.(DEGEN_TOKEN); handleToggle(); }); }; @@ -652,9 +652,9 @@ describe('SwapProvider', () => { it('should toggle tokens and amounts', async () => { const { result } = renderHook(() => useSwapContext(), { wrapper }); await act(async () => { - result.current.from.setToken(ETH_TOKEN); + result.current.from.setToken?.(ETH_TOKEN); result.current.from.setAmount('10'); - result.current.to.setToken(DEGEN_TOKEN); + result.current.to.setToken?.(DEGEN_TOKEN); result.current.to.setAmount('1000'); }); await act(async () => { diff --git a/src/swap/components/SwapProvider.tsx b/src/swap/components/SwapProvider.tsx index 97480fcf5a..7f8fc1824c 100644 --- a/src/swap/components/SwapProvider.tsx +++ b/src/swap/components/SwapProvider.tsx @@ -161,8 +161,8 @@ export function SwapProvider({ const handleToggle = useCallback(() => { from.setAmount(to.amount); to.setAmount(from.amount); - from.setToken(to.token); - to.setToken(from.token); + from.setToken?.(to.token); + to.setToken?.(from.token); updateLifecycleStatus({ statusName: 'amountChange', diff --git a/src/swap/hooks/useResetSwapLiteInputs.test.ts b/src/swap/hooks/useResetSwapLiteInputs.test.ts new file mode 100644 index 0000000000..a69744bfd1 --- /dev/null +++ b/src/swap/hooks/useResetSwapLiteInputs.test.ts @@ -0,0 +1,166 @@ +import { act, renderHook } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { SwapUnit } from '../types'; +import { useResetSwapLiteInputs } from './useResetSwapLiteInputs'; + +describe('useResetSwapLiteInputs', () => { + const mockFromTokenResponse = { + refetch: vi.fn().mockResolvedValue(undefined), + }; + const mockFromETHTokenResponse = { + refetch: vi.fn().mockResolvedValue(undefined), + }; + const mockFromUSDCTokenResponse = { + refetch: vi.fn().mockResolvedValue(undefined), + }; + const mockToTokenResponse = { refetch: vi.fn().mockResolvedValue(undefined) }; + const mockFrom: SwapUnit = { + balance: '100', + balanceResponse: mockFromTokenResponse, + amount: '50', + setAmount: vi.fn(), + setAmountUSD: vi.fn(), + token: undefined, + loading: false, + setLoading: vi.fn(), + error: undefined, + }; + const mockFromETH: SwapUnit = { + balance: '100', + balanceResponse: mockFromETHTokenResponse, + amount: '50', + setAmount: vi.fn(), + setAmountUSD: vi.fn(), + token: undefined, + loading: false, + setLoading: vi.fn(), + error: undefined, + }; + const mockFromUSDC: SwapUnit = { + balance: '100', + balanceResponse: mockFromUSDCTokenResponse, + amount: '50', + setAmount: vi.fn(), + setAmountUSD: vi.fn(), + token: undefined, + loading: false, + setLoading: vi.fn(), + error: undefined, + }; + const mockTo: SwapUnit = { + balance: '200', + balanceResponse: mockToTokenResponse, + amount: '75', + setAmount: vi.fn(), + setAmountUSD: vi.fn(), + token: undefined, + loading: false, + setLoading: vi.fn(), + error: undefined, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should return a function', () => { + const { result } = renderHook(() => + useResetSwapLiteInputs({ + fromETH: mockFromETH, + fromUSDC: mockFromUSDC, + from: mockFrom, + to: mockTo, + }), + ); + expect(typeof result.current).toBe('function'); + }); + + it('should call refetch on responses and set amounts to empty strings when executed', async () => { + const { result } = renderHook(() => + useResetSwapLiteInputs({ + fromETH: mockFromETH, + fromUSDC: mockFromUSDC, + from: mockFrom, + to: mockTo, + }), + ); + await act(async () => { + await result.current(); + }); + expect(mockFromETHTokenResponse.refetch).toHaveBeenCalledTimes(1); + expect(mockToTokenResponse.refetch).toHaveBeenCalledTimes(1); + expect(mockFromETH.setAmount).toHaveBeenCalledWith(''); + expect(mockFromETH.setAmountUSD).toHaveBeenCalledWith(''); + expect(mockTo.setAmount).toHaveBeenCalledWith(''); + expect(mockTo.setAmountUSD).toHaveBeenCalledWith(''); + }); + + it("should not create a new function reference if from and to haven't changed", () => { + const { result, rerender } = renderHook(() => + useResetSwapLiteInputs({ + fromETH: mockFromETH, + fromUSDC: mockFromUSDC, + to: mockTo, + }), + ); + const firstRender = result.current; + rerender(); + expect(result.current).toBe(firstRender); + }); + + it('should create a new function reference if from or to change', () => { + const { result, rerender } = renderHook( + ({ fromETH, fromUSDC, to }) => + useResetSwapLiteInputs({ + fromETH, + fromUSDC, + to, + }), + { + initialProps: { + fromETH: mockFromETH, + fromUSDC: mockFromUSDC, + to: mockTo, + }, + }, + ); + const firstRender = result.current; + const newMockFromETH = { + ...mockFromETH, + balanceResponse: { refetch: vi.fn().mockResolvedValue(undefined) }, + }; + const newMockFromUSDC = { + ...mockFromUSDC, + balanceResponse: { refetch: vi.fn().mockResolvedValue(undefined) }, + }; + rerender({ + fromETH: newMockFromETH, + fromUSDC: newMockFromUSDC, + to: mockTo, + }); + expect(result.current).not.toBe(firstRender); + }); + + it('should handle null responses gracefully', async () => { + const mockFromWithNullResponse = { ...mockFromETH, balanceResponse: null }; + const mockFromUSDCWithNullResponse = { + ...mockFromUSDC, + balanceResponse: null, + }; + const mockToWithNullResponse = { ...mockTo, balanceResponse: null }; + const { result } = renderHook(() => + useResetSwapLiteInputs({ + fromETH: mockFromWithNullResponse, + fromUSDC: mockFromUSDCWithNullResponse, + to: mockToWithNullResponse, + }), + ); + await act(async () => { + await result.current(); + }); + expect(mockFromWithNullResponse.setAmount).toHaveBeenCalledWith(''); + expect(mockFromWithNullResponse.setAmountUSD).toHaveBeenCalledWith(''); + expect(mockToWithNullResponse.setAmount).toHaveBeenCalledWith(''); + expect(mockToWithNullResponse.setAmountUSD).toHaveBeenCalledWith(''); + }); +}); diff --git a/src/swap/hooks/useResetSwapLiteInputs.ts b/src/swap/hooks/useResetSwapLiteInputs.ts new file mode 100644 index 0000000000..af62ad64ce --- /dev/null +++ b/src/swap/hooks/useResetSwapLiteInputs.ts @@ -0,0 +1,27 @@ +import { useCallback } from 'react'; +import type { SwapLiteTokens } from '../types'; + +// Refreshes balances and inputs post-swap +export const useResetSwapLiteInputs = ({ + fromETH, + fromUSDC, + from, + to, +}: SwapLiteTokens) => { + return useCallback(async () => { + await Promise.all([ + from?.balanceResponse?.refetch(), + from?.setAmount(''), + from?.setAmountUSD(''), + fromETH.balanceResponse?.refetch(), + fromETH.setAmount(''), + fromETH.setAmountUSD(''), + fromUSDC.balanceResponse?.refetch(), + fromUSDC.setAmount(''), + fromUSDC.setAmountUSD(''), + to.balanceResponse?.refetch(), + to.setAmount(''), + to.setAmountUSD(''), + ]); + }, [from, fromETH, fromUSDC, to]); +}; diff --git a/src/swap/hooks/useSwapLiteTokens.test.ts b/src/swap/hooks/useSwapLiteTokens.test.ts new file mode 100644 index 0000000000..f952a421de --- /dev/null +++ b/src/swap/hooks/useSwapLiteTokens.test.ts @@ -0,0 +1,132 @@ +import { act, renderHook } from '@testing-library/react'; +import { base } from 'viem/chains'; +import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; +import { useValue } from '../../core-react/internal/hooks/useValue'; +import type { Token } from '../../token'; +import { USDC_TOKEN } from '../mocks'; +import { useSwapLiteTokens } from './useSwapLiteTokens'; +import { useSwapBalances } from './useSwapBalances'; + +vi.mock('./useSwapBalances', () => ({ + useSwapBalances: vi.fn(), +})); + +vi.mock('../../core-react/internal/hooks/useValue', () => ({ + useValue: vi.fn(), +})); + +const toToken: Token = { + name: 'DEGEN', + address: '0x4ed4e862860bed51a9570b96d89af5e1b0efefed', + symbol: 'DEGEN', + decimals: 18, + image: + 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/3b/bf/3bbf118b5e6dc2f9e7fc607a6e7526647b4ba8f0bea87125f971446d57b296d2-MDNmNjY0MmEtNGFiZi00N2I0LWIwMTItMDUyMzg2ZDZhMWNm', + chainId: base.id, +}; + +const daiToken: Token = { + name: 'DAI', + address: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', + symbol: 'DAI', + decimals: 18, + image: + 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/92/13/9213e31b84c98a693f4c624580fdbe6e4c1cb550efbba15aa9ea68fd25ffb90c-ZTE1NmNjMGUtZGVkYi00ZDliLWI2N2QtNTY2ZWRjMmYwZmMw', + chainId: base.id, +}; + +describe('useSwapLiteTokens', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should return correct values', () => { + (useSwapBalances as Mock).mockReturnValue({ + fromBalanceString: '100', + fromTokenBalanceError: null, + fromTokenResponse: { refetch: vi.fn() }, + toBalanceString: '200', + toTokenBalanceError: null, + toTokenResponse: { refetch: vi.fn() }, + }); + (useValue as Mock).mockImplementation((props) => ({ + ...props, + amount: '100', + amountUSD: '150', + response: props.response, + setAmount: vi.fn(), + setAmountUSD: vi.fn(), + setLoading: vi.fn(), + token: USDC_TOKEN, + })); + const { result } = renderHook(() => + useSwapLiteTokens(toToken, undefined, '0x123'), + ); + expect(result.current.fromETH).toEqual({ + amount: '100', + amountUSD: '150', + balance: '100', + balanceResponse: { refetch: expect.any(Function) }, + error: null, + loading: false, + setAmount: expect.any(Function), + setAmountUSD: expect.any(Function), + setLoading: expect.any(Function), + token: USDC_TOKEN, + }); + expect(result.current.to).toEqual({ + amount: '100', + amountUSD: '150', + balance: '200', + balanceResponse: { refetch: expect.any(Function) }, + error: null, + loading: false, + setAmount: expect.any(Function), + setAmountUSD: expect.any(Function), + setLoading: expect.any(Function), + token: USDC_TOKEN, + }); + }); + + it('should call fromTokenResponse.refetch when fromETH.response.refetch is called', async () => { + const mockFromRefetch = vi.fn().mockResolvedValue(undefined); + const mockToRefetch = vi.fn().mockResolvedValue(undefined); + (useSwapBalances as Mock).mockReturnValue({ + fromTokenResponse: { refetch: mockFromRefetch }, + toTokenResponse: { refetch: mockToRefetch }, + }); + (useValue as Mock).mockImplementation((props) => ({ + ...props, + response: props.response, + })); + const { result } = renderHook(() => + useSwapLiteTokens(toToken, undefined, '0x123'), + ); + await act(async () => { + await result.current.fromETH.balanceResponse?.refetch(); + }); + expect(mockFromRefetch).toHaveBeenCalledTimes(1); + expect(mockToRefetch).not.toHaveBeenCalled(); + }); + + it('should call toTokenResponse.refetch when to.response.refetch is called', async () => { + const mockFromRefetch = vi.fn().mockResolvedValue(undefined); + const mockToRefetch = vi.fn().mockResolvedValue(undefined); + (useSwapBalances as Mock).mockReturnValue({ + fromTokenResponse: { refetch: mockFromRefetch }, + toTokenResponse: { refetch: mockToRefetch }, + }); + (useValue as Mock).mockImplementation((props) => ({ + ...props, + response: props.response, + })); + const { result } = renderHook(() => + useSwapLiteTokens(toToken, undefined, '0x123'), + ); + await act(async () => { + await result.current.to.balanceResponse?.refetch(); + }); + expect(mockToRefetch).toHaveBeenCalledTimes(1); + expect(mockFromRefetch).not.toHaveBeenCalled(); + }); +}); diff --git a/src/swap/hooks/useSwapLiteTokens.ts b/src/swap/hooks/useSwapLiteTokens.ts new file mode 100644 index 0000000000..81e7757ac5 --- /dev/null +++ b/src/swap/hooks/useSwapLiteTokens.ts @@ -0,0 +1,104 @@ +import { useState } from 'react'; +import type { Address } from 'viem'; +import { useValue } from '../../core-react/internal/hooks/useValue'; +import type { Token } from '../../token'; +import type { SwapLiteTokens } from '../types'; +import { useSwapBalances } from './useSwapBalances'; +import { ethToken, usdcToken } from '../constants'; + +export const useSwapLiteTokens = ( + toToken: Token, + fromToken?: Token, + address?: Address, +): SwapLiteTokens => { + const [toAmount, setToAmount] = useState(''); + const [toAmountUSD, setToAmountUSD] = useState(''); + const [toLoading, setToLoading] = useState(false); + + const [fromETHAmount, setFromETHAmount] = useState(''); + const [fromETHAmountUSD, setFromETHAmountUSD] = useState(''); + const [fromETHLoading, setFromETHLoading] = useState(false); + + const [fromUSDCAmount, setFromUSDCAmount] = useState(''); + const [fromUSDCAmountUSD, setFromUSDCAmountUSD] = useState(''); + const [fromUSDCLoading, setFromUSDCLoading] = useState(false); + + const [fromAmount, setFromAmount] = useState(''); + const [fromAmountUSD, setFromAmountUSD] = useState(''); + const [fromLoading, setFromLoading] = useState(false); + + const { + fromBalanceString: fromETHBalanceString, + fromTokenBalanceError: fromEthBalanceError, + toBalanceString, + toTokenBalanceError, + fromTokenResponse: fromETHResponse, + toTokenResponse, + } = useSwapBalances({ address, fromToken: ethToken, toToken }); + + const { + fromBalanceString: fromUSDCBalanceString, + fromTokenBalanceError: fromUSDCBalanceError, + fromTokenResponse: fromUSDCResponse, + } = useSwapBalances({ address, fromToken: usdcToken, toToken }); + + const { + fromBalanceString, + fromTokenBalanceError: fromBalanceError, + fromTokenResponse: fromResponse, + } = useSwapBalances({ address, fromToken, toToken }); + + const fromETH = useValue({ + balance: fromETHBalanceString, + balanceResponse: fromETHResponse, + amount: fromETHAmount, + setAmount: setFromETHAmount, + amountUSD: fromETHAmountUSD, + setAmountUSD: setFromETHAmountUSD, + token: ethToken, + loading: fromETHLoading, + setLoading: setFromETHLoading, + error: fromEthBalanceError, + }); + + const fromUSDC = useValue({ + balance: fromUSDCBalanceString, + balanceResponse: fromUSDCResponse, + amount: fromUSDCAmount, + setAmount: setFromUSDCAmount, + amountUSD: fromUSDCAmountUSD, + setAmountUSD: setFromUSDCAmountUSD, + token: usdcToken, + loading: fromUSDCLoading, + setLoading: setFromUSDCLoading, + error: fromUSDCBalanceError, + }); + + const from = useValue({ + balance: fromBalanceString, + balanceResponse: fromResponse, + amount: fromAmount, + setAmount: setFromAmount, + amountUSD: fromAmountUSD, + setAmountUSD: setFromAmountUSD, + token: fromToken, + loading: fromLoading, + setLoading: setFromLoading, + error: fromBalanceError, + }); + + const to = useValue({ + balance: toBalanceString, + balanceResponse: toTokenResponse, + amount: toAmount, + amountUSD: toAmountUSD, + setAmountUSD: setToAmountUSD, + setAmount: setToAmount, + token: toToken, + loading: toLoading, + setLoading: setToLoading, + error: toTokenBalanceError, + }); + + return { fromETH, from, fromUSDC, to }; +}; diff --git a/src/swap/types.ts b/src/swap/types.ts index 32fca9c145..44436dbcfc 100644 --- a/src/swap/types.ts +++ b/src/swap/types.ts @@ -54,14 +54,6 @@ export type FromTo = { to: SwapUnit; }; -export type FundSwapTokens = { - fromETH: FundSwapUnit; - fromUSDC: FundSwapUnit; - to: FundSwapUnit; -}; - -export type FundSwapUnit = Omit; - export type GetSwapMessageParams = { address?: Address; lifecycleStatus: LifecycleStatus; @@ -181,6 +173,13 @@ export type ProcessSwapTransactionParams = { walletCapabilities: WalletCapabilities; // EIP-5792 wallet capabilities }; +export type SwapLiteTokens = { + fromETH: SwapUnit; + fromUSDC: SwapUnit; + to: SwapUnit; + from?: SwapUnit; +}; + /** * Note: exported as public Type */ @@ -378,7 +377,7 @@ export type SwapUnit = { setAmount: Dispatch>; setAmountUSD: Dispatch>; setLoading: Dispatch>; - setToken: Dispatch>; + setToken?: Dispatch>; token: Token | undefined; }; From ca39272121b637817fbe6850908097706771929c Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Fri, 13 Dec 2024 11:38:49 -0800 Subject: [PATCH 05/16] fix imports --- src/swap/constants.ts | 2 +- src/swap/hooks/useSwapLiteTokens.test.ts | 2 +- src/swap/hooks/useSwapLiteTokens.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/swap/constants.ts b/src/swap/constants.ts index d2a04e933e..660626b193 100644 --- a/src/swap/constants.ts +++ b/src/swap/constants.ts @@ -1,5 +1,5 @@ -import type { Token } from '../token'; import { base } from 'viem/chains'; +import type { Token } from '../token'; export const FALLBACK_DEFAULT_MAX_SLIPPAGE = 3; export const GENERAL_SWAP_ERROR_CODE = 'SWAP_ERROR'; diff --git a/src/swap/hooks/useSwapLiteTokens.test.ts b/src/swap/hooks/useSwapLiteTokens.test.ts index f952a421de..001cb3526b 100644 --- a/src/swap/hooks/useSwapLiteTokens.test.ts +++ b/src/swap/hooks/useSwapLiteTokens.test.ts @@ -4,8 +4,8 @@ import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; import { useValue } from '../../core-react/internal/hooks/useValue'; import type { Token } from '../../token'; import { USDC_TOKEN } from '../mocks'; -import { useSwapLiteTokens } from './useSwapLiteTokens'; import { useSwapBalances } from './useSwapBalances'; +import { useSwapLiteTokens } from './useSwapLiteTokens'; vi.mock('./useSwapBalances', () => ({ useSwapBalances: vi.fn(), diff --git a/src/swap/hooks/useSwapLiteTokens.ts b/src/swap/hooks/useSwapLiteTokens.ts index 81e7757ac5..5a109d1269 100644 --- a/src/swap/hooks/useSwapLiteTokens.ts +++ b/src/swap/hooks/useSwapLiteTokens.ts @@ -2,9 +2,9 @@ import { useState } from 'react'; import type { Address } from 'viem'; import { useValue } from '../../core-react/internal/hooks/useValue'; import type { Token } from '../../token'; +import { ethToken, usdcToken } from '../constants'; import type { SwapLiteTokens } from '../types'; import { useSwapBalances } from './useSwapBalances'; -import { ethToken, usdcToken } from '../constants'; export const useSwapLiteTokens = ( toToken: Token, From dd2d3db43584aea126359a2391ca0180603dd6de Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Fri, 13 Dec 2024 12:07:01 -0800 Subject: [PATCH 06/16] add test coverage --- src/core/api/getSwapLiteQuote.test.ts | 202 ++++++++++++++++++++++++++ src/core/api/getSwapLiteQuote.ts | 11 +- 2 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 src/core/api/getSwapLiteQuote.test.ts diff --git a/src/core/api/getSwapLiteQuote.test.ts b/src/core/api/getSwapLiteQuote.test.ts new file mode 100644 index 0000000000..c26739118a --- /dev/null +++ b/src/core/api/getSwapLiteQuote.test.ts @@ -0,0 +1,202 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { getSwapLiteQuote } from './getSwapLiteQuote'; +import { getSwapQuote } from './getSwapQuote'; +import { formatTokenAmount } from '../utils/formatTokenAmount'; +import { isSwapError } from '../../swap/utils/isSwapError'; +import { Token } from '@/token/types'; +import { base } from 'viem/chains'; + +vi.mock('./getSwapQuote'); +vi.mock('../utils/formatTokenAmount'); +vi.mock('../../swap/utils/isSwapError'); + +const toToken: Token = { + name: 'DEGEN', + address: '0x4ed4e862860bed51a9570b96d89af5e1b0efefed', + symbol: 'DEGEN', + decimals: 18, + image: + 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/3b/bf/3bbf118b5e6dc2f9e7fc607a6e7526647b4ba8f0bea87125f971446d57b296d2-MDNmNjY0MmEtNGFiZi00N2I0LWIwMTItMDUyMzg2ZDZhMWNm', + chainId: base.id, +}; + +const fromToken: Token = { + name: 'ETH', + address: '', + symbol: 'ETH', + decimals: 18, + image: + 'https://wallet-api-production.s3.amazonaws.com/uploads/tokens/eth_288.png', + chainId: base.id, +}; + +const mockResponse = { + from: fromToken, + to: toToken, + fromAmount: '100000000000000000', + toAmount: '16732157880511600003860', + amountReference: 'from', + priceImpact: '0.07', + chainId: 8453, + hasHighPriceImpact: false, + slippage: '3', + fromAmountUSD: '100', +}; + +const mockEmptyResponse = { + from: fromToken, + to: toToken, + fromAmount: '', + toAmount: '16732157880511600003860', + amountReference: 'from', + priceImpact: '0.07', + chainId: 8453, + hasHighPriceImpact: false, + slippage: '3', + fromAmountUSD: '', +}; + +describe('getSwapLiteQuote', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + const mockFromSwapUnit = { + setAmountUSD: vi.fn(), + setAmount: vi.fn(), + }; + + it('should return default values if `from` token is not provided', async () => { + const result = await getSwapLiteQuote({ + amount: '1', + amountReference: 'exactIn', + maxSlippage: '0.5', + to: toToken, + useAggregator: true, + fromSwapUnit: mockFromSwapUnit, + }); + + expect(result).toEqual({ + response: undefined, + formattedFromAmount: '', + error: undefined, + }); + }); + + it('should call `getSwapQuote` if `from` and `to` tokens are different', async () => { + (getSwapQuote as jest.Mock).mockResolvedValue(mockResponse); + (isSwapError as jest.Mock).mockReturnValue(false); + (formatTokenAmount as jest.Mock).mockReturnValue('1.0'); + + const result = await getSwapLiteQuote({ + amount: '1', + amountReference: 'exactIn', + from: fromToken, + maxSlippage: '0.5', + to: toToken, + useAggregator: true, + fromSwapUnit: mockFromSwapUnit, + }); + + expect(getSwapQuote).toHaveBeenCalledWith({ + amount: '1', + amountReference: 'exactIn', + from: fromToken, + maxSlippage: '0.5', + to: toToken, + useAggregator: true, + }); + + expect(formatTokenAmount).toHaveBeenCalledWith('100000000000000000', 18); + expect(mockFromSwapUnit.setAmountUSD).toHaveBeenCalledWith('100'); + expect(mockFromSwapUnit.setAmount).toHaveBeenCalledWith('1.0'); + + expect(result).toEqual({ + response: mockResponse, + formattedFromAmount: '1.0', + error: undefined, + }); + }); + + it('should handle case where amount values are undefined', async () => { + (getSwapQuote as jest.Mock).mockResolvedValue(mockEmptyResponse); + (isSwapError as jest.Mock).mockReturnValue(false); + (formatTokenAmount as jest.Mock).mockReturnValue('1.0'); + + const result = await getSwapLiteQuote({ + amount: '1', + amountReference: 'exactIn', + from: fromToken, + maxSlippage: '0.5', + to: toToken, + useAggregator: true, + fromSwapUnit: mockFromSwapUnit, + }); + + expect(getSwapQuote).toHaveBeenCalledWith({ + amount: '1', + amountReference: 'exactIn', + from: fromToken, + maxSlippage: '0.5', + to: toToken, + useAggregator: true, + }); + + expect(formatTokenAmount).not.toHaveBeenCalled(); + expect(mockFromSwapUnit.setAmountUSD).toHaveBeenCalledWith(''); + expect(mockFromSwapUnit.setAmount).toHaveBeenCalledWith(''); + + expect(result).toEqual({ + response: mockEmptyResponse, + formattedFromAmount: '', + error: undefined, + }); + }); + + it('should handle swap errors correctly', async () => { + const mockError = { + code: 'UNCAUGHT_SWAP_QUOTE_ERROR', + error: 'Something went wrong', + message: '', + }; + + (getSwapQuote as jest.Mock).mockResolvedValue(mockError); + (isSwapError as jest.Mock).mockReturnValue(true); + + const result = await getSwapLiteQuote({ + amount: '1', + amountReference: 'exactIn', + from: fromToken, + maxSlippage: '0.5', + to: toToken, + useAggregator: true, + fromSwapUnit: mockFromSwapUnit, + }); + + expect(isSwapError).toHaveBeenCalledWith(mockError); + expect(result).toEqual({ + response: undefined, + formattedFromAmount: '', + error: mockError, + }); + }); + + it('should not call `getSwapQuote` if `from` and `to` tokens are the same', async () => { + const result = await getSwapLiteQuote({ + amount: '1', + amountReference: 'exactIn', + from: fromToken, + maxSlippage: '0.5', + to: fromToken, + useAggregator: true, + fromSwapUnit: mockFromSwapUnit, + }); + + expect(getSwapQuote).not.toHaveBeenCalled(); + expect(result).toEqual({ + response: undefined, + formattedFromAmount: '', + error: undefined, + }); + }); +}); diff --git a/src/core/api/getSwapLiteQuote.ts b/src/core/api/getSwapLiteQuote.ts index 6109bd8a59..5df835540e 100644 --- a/src/core/api/getSwapLiteQuote.ts +++ b/src/core/api/getSwapLiteQuote.ts @@ -20,6 +20,10 @@ type GetSwapLiteQuoteParams = Omit & { from?: Token; }; +/** + * Fetches a quote for a swap, but only if the from and to tokens are different. + */ + export async function getSwapLiteQuote({ amount, amountReference, @@ -49,10 +53,9 @@ export async function getSwapLiteQuote({ let formattedFromAmount = ''; if (response && !isSwapError(response)) { - formattedFromAmount = formatTokenAmount( - response.fromAmount, - response.from.decimals, - ); + formattedFromAmount = response?.fromAmount + ? formatTokenAmount(response.fromAmount, response.from.decimals) + : ''; fromSwapUnit.setAmountUSD(response?.fromAmountUSD || ''); fromSwapUnit.setAmount(formattedFromAmount || ''); From df789e99fef1091f1e819214e863e89d05c0efcc Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Fri, 13 Dec 2024 13:00:29 -0800 Subject: [PATCH 07/16] fix test --- src/core/api/getSwapLiteQuote.test.ts | 33 +++++++------ src/swap/hooks/useResetSwapLiteInputs.test.ts | 46 +++++++++++-------- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/src/core/api/getSwapLiteQuote.test.ts b/src/core/api/getSwapLiteQuote.test.ts index c26739118a..145293f158 100644 --- a/src/core/api/getSwapLiteQuote.test.ts +++ b/src/core/api/getSwapLiteQuote.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { describe, it, expect, vi, beforeEach, Mock } from 'vitest'; import { getSwapLiteQuote } from './getSwapLiteQuote'; import { getSwapQuote } from './getSwapQuote'; import { formatTokenAmount } from '../utils/formatTokenAmount'; @@ -56,16 +56,21 @@ const mockEmptyResponse = { fromAmountUSD: '', }; +const mockFromSwapUnit = { + setAmountUSD: vi.fn(), + setAmount: vi.fn(), + amount: '1', + amountUSD: '1', + loading: false, + setLoading: vi.fn(), + token: fromToken, +}; + describe('getSwapLiteQuote', () => { beforeEach(() => { vi.clearAllMocks(); }); - const mockFromSwapUnit = { - setAmountUSD: vi.fn(), - setAmount: vi.fn(), - }; - it('should return default values if `from` token is not provided', async () => { const result = await getSwapLiteQuote({ amount: '1', @@ -84,9 +89,9 @@ describe('getSwapLiteQuote', () => { }); it('should call `getSwapQuote` if `from` and `to` tokens are different', async () => { - (getSwapQuote as jest.Mock).mockResolvedValue(mockResponse); - (isSwapError as jest.Mock).mockReturnValue(false); - (formatTokenAmount as jest.Mock).mockReturnValue('1.0'); + (getSwapQuote as Mock).mockResolvedValue(mockResponse); + (isSwapError as unknown as Mock).mockReturnValue(false); + (formatTokenAmount as Mock).mockReturnValue('1.0'); const result = await getSwapLiteQuote({ amount: '1', @@ -119,9 +124,9 @@ describe('getSwapLiteQuote', () => { }); it('should handle case where amount values are undefined', async () => { - (getSwapQuote as jest.Mock).mockResolvedValue(mockEmptyResponse); - (isSwapError as jest.Mock).mockReturnValue(false); - (formatTokenAmount as jest.Mock).mockReturnValue('1.0'); + (getSwapQuote as Mock).mockResolvedValue(mockEmptyResponse); + (isSwapError as unknown as Mock).mockReturnValue(false); + (formatTokenAmount as Mock).mockReturnValue('1.0'); const result = await getSwapLiteQuote({ amount: '1', @@ -160,8 +165,8 @@ describe('getSwapLiteQuote', () => { message: '', }; - (getSwapQuote as jest.Mock).mockResolvedValue(mockError); - (isSwapError as jest.Mock).mockReturnValue(true); + (getSwapQuote as Mock).mockResolvedValue(mockError); + (isSwapError as unknown as Mock).mockReturnValue(true); const result = await getSwapLiteQuote({ amount: '1', diff --git a/src/swap/hooks/useResetSwapLiteInputs.test.ts b/src/swap/hooks/useResetSwapLiteInputs.test.ts index a69744bfd1..9fb0ee5f83 100644 --- a/src/swap/hooks/useResetSwapLiteInputs.test.ts +++ b/src/swap/hooks/useResetSwapLiteInputs.test.ts @@ -4,16 +4,20 @@ import type { SwapUnit } from '../types'; import { useResetSwapLiteInputs } from './useResetSwapLiteInputs'; describe('useResetSwapLiteInputs', () => { - const mockFromTokenResponse = { + const mockQueryResponse = { + data: undefined, + error: null, + isError: false, + isPending: true, + isSuccess: false, + status: 'pending', refetch: vi.fn().mockResolvedValue(undefined), - }; - const mockFromETHTokenResponse = { - refetch: vi.fn().mockResolvedValue(undefined), - }; - const mockFromUSDCTokenResponse = { - refetch: vi.fn().mockResolvedValue(undefined), - }; - const mockToTokenResponse = { refetch: vi.fn().mockResolvedValue(undefined) }; + } as const; + + const mockFromTokenResponse = mockQueryResponse; + const mockFromETHTokenResponse = mockQueryResponse; + const mockFromUSDCTokenResponse = mockQueryResponse; + const mockToTokenResponse = mockQueryResponse; const mockFrom: SwapUnit = { balance: '100', balanceResponse: mockFromTokenResponse, @@ -24,7 +28,7 @@ describe('useResetSwapLiteInputs', () => { loading: false, setLoading: vi.fn(), error: undefined, - }; + } as unknown as SwapUnit; const mockFromETH: SwapUnit = { balance: '100', balanceResponse: mockFromETHTokenResponse, @@ -35,7 +39,7 @@ describe('useResetSwapLiteInputs', () => { loading: false, setLoading: vi.fn(), error: undefined, - }; + } as unknown as SwapUnit; const mockFromUSDC: SwapUnit = { balance: '100', balanceResponse: mockFromUSDCTokenResponse, @@ -46,7 +50,7 @@ describe('useResetSwapLiteInputs', () => { loading: false, setLoading: vi.fn(), error: undefined, - }; + } as unknown as SwapUnit; const mockTo: SwapUnit = { balance: '200', balanceResponse: mockToTokenResponse, @@ -57,7 +61,7 @@ describe('useResetSwapLiteInputs', () => { loading: false, setLoading: vi.fn(), error: undefined, - }; + } as unknown as SwapUnit; beforeEach(() => { vi.clearAllMocks(); @@ -128,11 +132,11 @@ describe('useResetSwapLiteInputs', () => { const newMockFromETH = { ...mockFromETH, balanceResponse: { refetch: vi.fn().mockResolvedValue(undefined) }, - }; + } as unknown as SwapUnit; const newMockFromUSDC = { ...mockFromUSDC, balanceResponse: { refetch: vi.fn().mockResolvedValue(undefined) }, - }; + } as unknown as SwapUnit; rerender({ fromETH: newMockFromETH, fromUSDC: newMockFromUSDC, @@ -142,12 +146,18 @@ describe('useResetSwapLiteInputs', () => { }); it('should handle null responses gracefully', async () => { - const mockFromWithNullResponse = { ...mockFromETH, balanceResponse: null }; + const mockFromWithNullResponse = { + ...mockFromETH, + balanceResponse: null, + } as unknown as SwapUnit; const mockFromUSDCWithNullResponse = { ...mockFromUSDC, balanceResponse: null, - }; - const mockToWithNullResponse = { ...mockTo, balanceResponse: null }; + } as unknown as SwapUnit; + const mockToWithNullResponse = { + ...mockTo, + balanceResponse: null, + } as unknown as SwapUnit; const { result } = renderHook(() => useResetSwapLiteInputs({ fromETH: mockFromWithNullResponse, From ac84347a50b574744b42638324c6dcac6b90caf3 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Fri, 13 Dec 2024 13:05:20 -0800 Subject: [PATCH 08/16] fix lint --- src/core/api/getSwapLiteQuote.test.ts | 10 +++++----- src/swap/hooks/useSwapLiteTokens.test.ts | 10 ---------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/core/api/getSwapLiteQuote.test.ts b/src/core/api/getSwapLiteQuote.test.ts index 145293f158..59223cf707 100644 --- a/src/core/api/getSwapLiteQuote.test.ts +++ b/src/core/api/getSwapLiteQuote.test.ts @@ -1,10 +1,10 @@ -import { describe, it, expect, vi, beforeEach, Mock } from 'vitest'; +import type { Token } from '@/token/types'; +import { base } from 'viem/chains'; +import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'; +import { isSwapError } from '../../swap/utils/isSwapError'; +import { formatTokenAmount } from '../utils/formatTokenAmount'; import { getSwapLiteQuote } from './getSwapLiteQuote'; import { getSwapQuote } from './getSwapQuote'; -import { formatTokenAmount } from '../utils/formatTokenAmount'; -import { isSwapError } from '../../swap/utils/isSwapError'; -import { Token } from '@/token/types'; -import { base } from 'viem/chains'; vi.mock('./getSwapQuote'); vi.mock('../utils/formatTokenAmount'); diff --git a/src/swap/hooks/useSwapLiteTokens.test.ts b/src/swap/hooks/useSwapLiteTokens.test.ts index 001cb3526b..65b4b19877 100644 --- a/src/swap/hooks/useSwapLiteTokens.test.ts +++ b/src/swap/hooks/useSwapLiteTokens.test.ts @@ -25,16 +25,6 @@ const toToken: Token = { chainId: base.id, }; -const daiToken: Token = { - name: 'DAI', - address: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', - symbol: 'DAI', - decimals: 18, - image: - 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/92/13/9213e31b84c98a693f4c624580fdbe6e4c1cb550efbba15aa9ea68fd25ffb90c-ZTE1NmNjMGUtZGVkYi00ZDliLWI2N2QtNTY2ZWRjMmYwZmMw', - chainId: base.id, -}; - describe('useSwapLiteTokens', () => { beforeEach(() => { vi.clearAllMocks(); From c727598f884b57cd0826418d7295c96e98196c39 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Fri, 13 Dec 2024 13:09:42 -0800 Subject: [PATCH 09/16] fix lint --- src/core/api/getSwapLiteQuote.test.ts | 2 +- src/swap/components/SwapAmountInput.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/api/getSwapLiteQuote.test.ts b/src/core/api/getSwapLiteQuote.test.ts index 59223cf707..70405de484 100644 --- a/src/core/api/getSwapLiteQuote.test.ts +++ b/src/core/api/getSwapLiteQuote.test.ts @@ -1,6 +1,6 @@ import type { Token } from '@/token/types'; import { base } from 'viem/chains'; -import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'; +import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; import { isSwapError } from '../../swap/utils/isSwapError'; import { formatTokenAmount } from '../utils/formatTokenAmount'; import { getSwapLiteQuote } from './getSwapLiteQuote'; diff --git a/src/swap/components/SwapAmountInput.tsx b/src/swap/components/SwapAmountInput.tsx index f6b9cc28c8..4e7082d7e3 100644 --- a/src/swap/components/SwapAmountInput.tsx +++ b/src/swap/components/SwapAmountInput.tsx @@ -31,7 +31,7 @@ export function SwapAmountInput({ const destination = useValue(type === 'from' ? to : from); useEffect(() => { if (token) { - source.setToken(token); + source.setToken?.(token); } }, [token, source.setToken]); @@ -52,7 +52,7 @@ export function SwapAmountInput({ const handleSetToken = useCallback( (token: Token) => { - source.setToken(token); + source.setToken?.(token); handleAmountChange(type, source.amount, token); }, [source.amount, source.setToken, handleAmountChange, type], From 501a141a4149fa3588d124472c41831094655880 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Fri, 13 Dec 2024 13:14:21 -0800 Subject: [PATCH 10/16] fix test --- src/swap/hooks/useResetSwapLiteInputs.test.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/swap/hooks/useResetSwapLiteInputs.test.ts b/src/swap/hooks/useResetSwapLiteInputs.test.ts index 9fb0ee5f83..c1232384c5 100644 --- a/src/swap/hooks/useResetSwapLiteInputs.test.ts +++ b/src/swap/hooks/useResetSwapLiteInputs.test.ts @@ -11,13 +11,24 @@ describe('useResetSwapLiteInputs', () => { isPending: true, isSuccess: false, status: 'pending', - refetch: vi.fn().mockResolvedValue(undefined), } as const; - const mockFromTokenResponse = mockQueryResponse; - const mockFromETHTokenResponse = mockQueryResponse; - const mockFromUSDCTokenResponse = mockQueryResponse; - const mockToTokenResponse = mockQueryResponse; + const mockFromTokenResponse = { + ...mockQueryResponse, + refetch: vi.fn().mockResolvedValue(undefined), + }; + const mockFromETHTokenResponse = { + ...mockQueryResponse, + refetch: vi.fn().mockResolvedValue(undefined), + }; + const mockFromUSDCTokenResponse = { + ...mockQueryResponse, + refetch: vi.fn().mockResolvedValue(undefined), + }; + const mockToTokenResponse = { + ...mockQueryResponse, + refetch: vi.fn().mockResolvedValue(undefined), + }; const mockFrom: SwapUnit = { balance: '100', balanceResponse: mockFromTokenResponse, From 33b694a8771c99a085e5af79d5d8fbb04ce72362 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Fri, 13 Dec 2024 13:59:58 -0800 Subject: [PATCH 11/16] move util --- .../api => swap/utils}/getSwapLiteQuote.test.ts | 4 ++-- src/{core/api => swap/utils}/getSwapLiteQuote.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) rename src/{core/api => swap/utils}/getSwapLiteQuote.test.ts (97%) rename src/{core/api => swap/utils}/getSwapLiteQuote.ts (91%) diff --git a/src/core/api/getSwapLiteQuote.test.ts b/src/swap/utils/getSwapLiteQuote.test.ts similarity index 97% rename from src/core/api/getSwapLiteQuote.test.ts rename to src/swap/utils/getSwapLiteQuote.test.ts index 70405de484..4dbef4399c 100644 --- a/src/core/api/getSwapLiteQuote.test.ts +++ b/src/swap/utils/getSwapLiteQuote.test.ts @@ -1,10 +1,10 @@ +import { getSwapQuote } from '@/core/api/getSwapQuote'; +import { formatTokenAmount } from '@/core/utils/formatTokenAmount'; import type { Token } from '@/token/types'; import { base } from 'viem/chains'; import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; import { isSwapError } from '../../swap/utils/isSwapError'; -import { formatTokenAmount } from '../utils/formatTokenAmount'; import { getSwapLiteQuote } from './getSwapLiteQuote'; -import { getSwapQuote } from './getSwapQuote'; vi.mock('./getSwapQuote'); vi.mock('../utils/formatTokenAmount'); diff --git a/src/core/api/getSwapLiteQuote.ts b/src/swap/utils/getSwapLiteQuote.ts similarity index 91% rename from src/core/api/getSwapLiteQuote.ts rename to src/swap/utils/getSwapLiteQuote.ts index 5df835540e..cd1009a76d 100644 --- a/src/core/api/getSwapLiteQuote.ts +++ b/src/swap/utils/getSwapLiteQuote.ts @@ -1,13 +1,13 @@ -import type { SwapError, SwapUnit } from '../../swap/types'; -import { isSwapError } from '../../swap/utils/isSwapError'; -import type { Token } from '../../token'; -import { formatTokenAmount } from '../utils/formatTokenAmount'; -import { getSwapQuote } from './getSwapQuote'; +import { getSwapQuote } from '@/core/api/getSwapQuote'; import type { APIError, GetSwapQuoteParams, GetSwapQuoteResponse, -} from './types'; +} from '@/core/api/types'; +import { formatTokenAmount } from '@/core/utils/formatTokenAmount'; +import type { SwapError, SwapUnit } from '../../swap/types'; +import { isSwapError } from '../../swap/utils/isSwapError'; +import type { Token } from '../../token'; type GetSwapLiteQuoteResponse = { response?: GetSwapQuoteResponse; From 62980395446891e5bb0fbd8699dc5081e2d0809d Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Fri, 13 Dec 2024 14:03:25 -0800 Subject: [PATCH 12/16] fix test --- src/swap/utils/getSwapLiteQuote.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/swap/utils/getSwapLiteQuote.test.ts b/src/swap/utils/getSwapLiteQuote.test.ts index 4dbef4399c..b31ec306e2 100644 --- a/src/swap/utils/getSwapLiteQuote.test.ts +++ b/src/swap/utils/getSwapLiteQuote.test.ts @@ -6,8 +6,8 @@ import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; import { isSwapError } from '../../swap/utils/isSwapError'; import { getSwapLiteQuote } from './getSwapLiteQuote'; -vi.mock('./getSwapQuote'); -vi.mock('../utils/formatTokenAmount'); +vi.mock('@/core/api/getSwapQuote'); +vi.mock('@/core/utils/formatTokenAmount'); vi.mock('../../swap/utils/isSwapError'); const toToken: Token = { From 8cedbe0a940dca6c961a9ae9ccac2019d35f6cde Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Fri, 13 Dec 2024 14:07:40 -0800 Subject: [PATCH 13/16] move tokens --- src/swap/constants.ts | 20 -------------------- src/swap/hooks/useSwapLiteTokens.ts | 2 +- src/token/constants.ts | 22 ++++++++++++++++++++++ 3 files changed, 23 insertions(+), 21 deletions(-) create mode 100644 src/token/constants.ts diff --git a/src/swap/constants.ts b/src/swap/constants.ts index 660626b193..01cf15b9eb 100644 --- a/src/swap/constants.ts +++ b/src/swap/constants.ts @@ -26,23 +26,3 @@ export enum SwapMessage { TOO_MANY_REQUESTS = 'Too many requests. Please try again later.', USER_REJECTED = 'User rejected the transaction', } - -export const ethToken: Token = { - name: 'ETH', - address: '', - symbol: 'ETH', - decimals: 18, - image: - 'https://wallet-api-production.s3.amazonaws.com/uploads/tokens/eth_288.png', - chainId: base.id, -}; - -export const usdcToken: Token = { - name: 'USDC', - address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', - symbol: 'USDC', - decimals: 6, - image: - 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/44/2b/442b80bd16af0c0d9b22e03a16753823fe826e5bfd457292b55fa0ba8c1ba213-ZWUzYjJmZGUtMDYxNy00NDcyLTg0NjQtMWI4OGEwYjBiODE2', - chainId: base.id, -}; diff --git a/src/swap/hooks/useSwapLiteTokens.ts b/src/swap/hooks/useSwapLiteTokens.ts index 5a109d1269..1bbaa59452 100644 --- a/src/swap/hooks/useSwapLiteTokens.ts +++ b/src/swap/hooks/useSwapLiteTokens.ts @@ -2,7 +2,7 @@ import { useState } from 'react'; import type { Address } from 'viem'; import { useValue } from '../../core-react/internal/hooks/useValue'; import type { Token } from '../../token'; -import { ethToken, usdcToken } from '../constants'; +import { ethToken, usdcToken } from '../../token/constants'; import type { SwapLiteTokens } from '../types'; import { useSwapBalances } from './useSwapBalances'; diff --git a/src/token/constants.ts b/src/token/constants.ts new file mode 100644 index 0000000000..01eed2570f --- /dev/null +++ b/src/token/constants.ts @@ -0,0 +1,22 @@ +import { base } from 'viem/chains'; +import type { Token } from './types'; + +export const ethToken: Token = { + name: 'ETH', + address: '', + symbol: 'ETH', + decimals: 18, + image: + 'https://wallet-api-production.s3.amazonaws.com/uploads/tokens/eth_288.png', + chainId: base.id, +}; + +export const usdcToken: Token = { + name: 'USDC', + address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', + symbol: 'USDC', + decimals: 6, + image: + 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/44/2b/442b80bd16af0c0d9b22e03a16753823fe826e5bfd457292b55fa0ba8c1ba213-ZWUzYjJmZGUtMDYxNy00NDcyLTg0NjQtMWI4OGEwYjBiODE2', + chainId: base.id, +}; From 3873823b15575687d14721f78a1a75e34739227e Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Fri, 13 Dec 2024 14:20:39 -0800 Subject: [PATCH 14/16] fix lint --- src/swap/constants.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/swap/constants.ts b/src/swap/constants.ts index 01cf15b9eb..ad222a5704 100644 --- a/src/swap/constants.ts +++ b/src/swap/constants.ts @@ -1,6 +1,3 @@ -import { base } from 'viem/chains'; -import type { Token } from '../token'; - export const FALLBACK_DEFAULT_MAX_SLIPPAGE = 3; export const GENERAL_SWAP_ERROR_CODE = 'SWAP_ERROR'; export const GENERAL_SWAP_QUOTE_ERROR_CODE = 'SWAP_QUOTE_ERROR'; From d8761806ec955a24d4baf291157077dc30f05021 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Fri, 13 Dec 2024 15:21:22 -0800 Subject: [PATCH 15/16] refactor useSwapLiteTokens hook --- src/swap/hooks/useSwapLiteToken.test.ts | 82 ++++++++++++ src/swap/hooks/useSwapLiteToken.ts | 34 +++++ src/swap/hooks/useSwapLiteTokens.test.ts | 162 ++++++++++++++--------- src/swap/hooks/useSwapLiteTokens.ts | 90 +++---------- src/token/constants.ts | 20 +++ 5 files changed, 251 insertions(+), 137 deletions(-) create mode 100644 src/swap/hooks/useSwapLiteToken.test.ts create mode 100644 src/swap/hooks/useSwapLiteToken.ts diff --git a/src/swap/hooks/useSwapLiteToken.test.ts b/src/swap/hooks/useSwapLiteToken.test.ts new file mode 100644 index 0000000000..4d4d6dc9fd --- /dev/null +++ b/src/swap/hooks/useSwapLiteToken.test.ts @@ -0,0 +1,82 @@ +import { act, renderHook } from '@testing-library/react'; +import { base } from 'viem/chains'; +import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; +import { useValue } from '../../core-react/internal/hooks/useValue'; +import type { Token } from '../../token'; +import { usdcToken } from '../../token/constants'; +import { useSwapBalances } from './useSwapBalances'; +import { useSwapLiteToken } from './useSwapLiteToken'; + +vi.mock('./useSwapBalances', () => ({ + useSwapBalances: vi.fn(), +})); + +vi.mock('../../core-react/internal/hooks/useValue', () => ({ + useValue: vi.fn(), +})); + +const toToken: Token = { + name: 'DEGEN', + address: '0x4ed4e862860bed51a9570b96d89af5e1b0efefed', + symbol: 'DEGEN', + decimals: 18, + image: + 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/3b/bf/3bbf118b5e6dc2f9e7fc607a6e7526647b4ba8f0bea87125f971446d57b296d2-MDNmNjY0MmEtNGFiZi00N2I0LWIwMTItMDUyMzg2ZDZhMWNm', + chainId: base.id, +}; + +describe('useSwapLiteToken', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should return correct values', () => { + (useSwapBalances as Mock).mockReturnValue({ + fromBalanceString: '100', + fromTokenBalanceError: null, + fromTokenResponse: { refetch: vi.fn() }, + toBalanceString: '200', + toTokenBalanceError: null, + toTokenResponse: { refetch: vi.fn() }, + }); + (useValue as Mock).mockImplementation((props) => ({ + ...props, + })); + const { result } = renderHook(() => + useSwapLiteToken(toToken, usdcToken, '0x123'), + ); + expect(result.current).toEqual({ + amount: '', + amountUSD: '', + balance: '100', + balanceResponse: { refetch: expect.any(Function) }, + error: null, + loading: false, + setAmount: expect.any(Function), + setAmountUSD: expect.any(Function), + setLoading: expect.any(Function), + token: usdcToken, + }); + }); + + it('should call fromTokenResponse.refetch when fromETH.response.refetch is called', async () => { + const mockFromRefetch = vi.fn().mockResolvedValue(undefined); + const mockToRefetch = vi.fn().mockResolvedValue(undefined); + (useSwapBalances as Mock).mockReturnValue({ + fromTokenResponse: { refetch: mockFromRefetch }, + toTokenResponse: { refetch: mockToRefetch }, + }); + (useValue as Mock).mockImplementation((props) => ({ + ...props, + response: props.response, + })); + const { result } = renderHook(() => + useSwapLiteToken(toToken, usdcToken, '0x123'), + ); + await act(async () => { + await result.current.balanceResponse?.refetch(); + }); + expect(mockFromRefetch).toHaveBeenCalledTimes(1); + expect(mockToRefetch).not.toHaveBeenCalled(); + }); +}); diff --git a/src/swap/hooks/useSwapLiteToken.ts b/src/swap/hooks/useSwapLiteToken.ts new file mode 100644 index 0000000000..8c3f620517 --- /dev/null +++ b/src/swap/hooks/useSwapLiteToken.ts @@ -0,0 +1,34 @@ +import { useState } from 'react'; +import type { Address } from 'viem'; +import { useValue } from '../../core-react/internal/hooks/useValue'; +import type { Token } from '../../token'; +import { useSwapBalances } from './useSwapBalances'; + +export const useSwapLiteToken = ( + toToken: Token, + token: Token | undefined, + address: Address | undefined, +) => { + const [amount, setAmount] = useState(''); + const [amountUSD, setAmountUSD] = useState(''); + const [loading, setLoading] = useState(false); + + const { + fromBalanceString: balance, + fromTokenBalanceError: error, + fromTokenResponse: balanceResponse, + } = useSwapBalances({ address, fromToken: token, toToken }); + + return useValue({ + balance, + balanceResponse, + amount, + setAmount, + amountUSD, + setAmountUSD, + token, + loading, + setLoading, + error, + }); +}; diff --git a/src/swap/hooks/useSwapLiteTokens.test.ts b/src/swap/hooks/useSwapLiteTokens.test.ts index 65b4b19877..402527c2a3 100644 --- a/src/swap/hooks/useSwapLiteTokens.test.ts +++ b/src/swap/hooks/useSwapLiteTokens.test.ts @@ -3,17 +3,19 @@ import { base } from 'viem/chains'; import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; import { useValue } from '../../core-react/internal/hooks/useValue'; import type { Token } from '../../token'; -import { USDC_TOKEN } from '../mocks'; import { useSwapBalances } from './useSwapBalances'; import { useSwapLiteTokens } from './useSwapLiteTokens'; +import { useSwapLiteToken } from './useSwapLiteToken'; +import { + daiToken, + degenToken, + ethToken, + usdcToken, +} from '../../token/constants'; -vi.mock('./useSwapBalances', () => ({ - useSwapBalances: vi.fn(), -})); - -vi.mock('../../core-react/internal/hooks/useValue', () => ({ - useValue: vi.fn(), -})); +vi.mock('./useSwapLiteToken'); +vi.mock('./useSwapBalances'); +vi.mock('../../core-react/internal/hooks/useValue'); const toToken: Token = { name: 'DEGEN', @@ -25,78 +27,112 @@ const toToken: Token = { chainId: base.id, }; +const mockFromETH = { + balance: '100', + balanceResponse: { refetch: vi.fn() }, + error: null, + loading: false, + setAmount: vi.fn(), + setAmountUSD: vi.fn(), + setLoading: vi.fn(), + token: ethToken, +}; + +const mockFromUSDC = { + balance: '50', + balanceResponse: { refetch: vi.fn() }, + error: null, + loading: false, + setAmount: vi.fn(), + setAmountUSD: vi.fn(), + setLoading: vi.fn(), + token: usdcToken, +}; + +const mockFrom = { + balance: '50', + balanceResponse: { refetch: vi.fn() }, + error: null, + loading: false, + setAmount: vi.fn(), + setAmountUSD: vi.fn(), + setLoading: vi.fn(), + token: degenToken, +}; + +const mockTo = { + balance: '1000', + balanceResponse: { refetch: vi.fn() }, + error: null, + loading: false, + setAmount: vi.fn(), + setAmountUSD: vi.fn(), + setLoading: vi.fn(), + token: daiToken, +}; + +const address = '0x123'; + describe('useSwapLiteTokens', () => { beforeEach(() => { vi.clearAllMocks(); - }); + (useSwapLiteToken as Mock).mockImplementation((toToken, fromToken) => { + if (fromToken === ethToken) return mockFromETH; + if (fromToken === usdcToken) return mockFromUSDC; + return mockFrom; + }); - it('should return correct values', () => { (useSwapBalances as Mock).mockReturnValue({ - fromBalanceString: '100', - fromTokenBalanceError: null, - fromTokenResponse: { refetch: vi.fn() }, - toBalanceString: '200', + toBalanceString: '1000', toTokenBalanceError: null, - toTokenResponse: { refetch: vi.fn() }, + toTokenResponse: { balance: '1000' }, }); - (useValue as Mock).mockImplementation((props) => ({ - ...props, - amount: '100', - amountUSD: '150', - response: props.response, - setAmount: vi.fn(), - setAmountUSD: vi.fn(), - setLoading: vi.fn(), - token: USDC_TOKEN, - })); + + (useValue as Mock).mockReturnValue(mockTo); + }); + + it('should return expected swap tokens', () => { const { result } = renderHook(() => - useSwapLiteTokens(toToken, undefined, '0x123'), + useSwapLiteTokens(toToken, daiToken, address), ); - expect(result.current.fromETH).toEqual({ - amount: '100', - amountUSD: '150', - balance: '100', - balanceResponse: { refetch: expect.any(Function) }, - error: null, - loading: false, - setAmount: expect.any(Function), - setAmountUSD: expect.any(Function), - setLoading: expect.any(Function), - token: USDC_TOKEN, + + expect(useSwapLiteToken).toHaveBeenCalledWith(toToken, ethToken, address); + expect(useSwapLiteToken).toHaveBeenCalledWith(toToken, usdcToken, address); + expect(useSwapLiteToken).toHaveBeenCalledWith(toToken, daiToken, address); + expect(useSwapBalances).toHaveBeenCalledWith({ + address, + fromToken: ethToken, + toToken, }); - expect(result.current.to).toEqual({ - amount: '100', - amountUSD: '150', - balance: '200', - balanceResponse: { refetch: expect.any(Function) }, - error: null, - loading: false, + expect(useValue).toHaveBeenCalledWith({ + balance: '1000', + balanceResponse: { balance: '1000' }, + amount: '', setAmount: expect.any(Function), + amountUSD: '', setAmountUSD: expect.any(Function), + token: toToken, + loading: false, setLoading: expect.any(Function), - token: USDC_TOKEN, + error: null, }); - }); - it('should call fromTokenResponse.refetch when fromETH.response.refetch is called', async () => { - const mockFromRefetch = vi.fn().mockResolvedValue(undefined); - const mockToRefetch = vi.fn().mockResolvedValue(undefined); - (useSwapBalances as Mock).mockReturnValue({ - fromTokenResponse: { refetch: mockFromRefetch }, - toTokenResponse: { refetch: mockToRefetch }, + expect(result.current).toEqual({ + fromETH: mockFromETH, + fromUSDC: mockFromUSDC, + from: mockFrom, + to: mockTo, }); - (useValue as Mock).mockImplementation((props) => ({ - ...props, - response: props.response, - })); - const { result } = renderHook(() => - useSwapLiteTokens(toToken, undefined, '0x123'), - ); - await act(async () => { - await result.current.fromETH.balanceResponse?.refetch(); + }); + + it('should handle toToken.symbol === ETH', () => { + renderHook(() => useSwapLiteTokens(ethToken, degenToken, address)); + + expect(useSwapBalances).toHaveBeenCalledWith({ + address, + fromToken: usdcToken, + toToken: ethToken, }); - expect(mockFromRefetch).toHaveBeenCalledTimes(1); - expect(mockToRefetch).not.toHaveBeenCalled(); }); it('should call toTokenResponse.refetch when to.response.refetch is called', async () => { diff --git a/src/swap/hooks/useSwapLiteTokens.ts b/src/swap/hooks/useSwapLiteTokens.ts index 1bbaa59452..e364df6faf 100644 --- a/src/swap/hooks/useSwapLiteTokens.ts +++ b/src/swap/hooks/useSwapLiteTokens.ts @@ -5,100 +5,42 @@ import type { Token } from '../../token'; import { ethToken, usdcToken } from '../../token/constants'; import type { SwapLiteTokens } from '../types'; import { useSwapBalances } from './useSwapBalances'; +import { useSwapLiteToken } from './useSwapLiteToken'; export const useSwapLiteTokens = ( toToken: Token, fromToken?: Token, address?: Address, ): SwapLiteTokens => { + const fromETH = useSwapLiteToken(toToken, ethToken, address); + const fromUSDC = useSwapLiteToken(toToken, usdcToken, address); + const from = useSwapLiteToken(toToken, fromToken, address); + const [toAmount, setToAmount] = useState(''); const [toAmountUSD, setToAmountUSD] = useState(''); const [toLoading, setToLoading] = useState(false); - const [fromETHAmount, setFromETHAmount] = useState(''); - const [fromETHAmountUSD, setFromETHAmountUSD] = useState(''); - const [fromETHLoading, setFromETHLoading] = useState(false); - - const [fromUSDCAmount, setFromUSDCAmount] = useState(''); - const [fromUSDCAmountUSD, setFromUSDCAmountUSD] = useState(''); - const [fromUSDCLoading, setFromUSDCLoading] = useState(false); - - const [fromAmount, setFromAmount] = useState(''); - const [fromAmountUSD, setFromAmountUSD] = useState(''); - const [fromLoading, setFromLoading] = useState(false); - - const { - fromBalanceString: fromETHBalanceString, - fromTokenBalanceError: fromEthBalanceError, - toBalanceString, - toTokenBalanceError, - fromTokenResponse: fromETHResponse, - toTokenResponse, - } = useSwapBalances({ address, fromToken: ethToken, toToken }); - - const { - fromBalanceString: fromUSDCBalanceString, - fromTokenBalanceError: fromUSDCBalanceError, - fromTokenResponse: fromUSDCResponse, - } = useSwapBalances({ address, fromToken: usdcToken, toToken }); + // If the toToken is ETH, use USDC for swapQuote + const token = toToken?.symbol === 'ETH' ? usdcToken : ethToken; const { - fromBalanceString, - fromTokenBalanceError: fromBalanceError, - fromTokenResponse: fromResponse, - } = useSwapBalances({ address, fromToken, toToken }); - - const fromETH = useValue({ - balance: fromETHBalanceString, - balanceResponse: fromETHResponse, - amount: fromETHAmount, - setAmount: setFromETHAmount, - amountUSD: fromETHAmountUSD, - setAmountUSD: setFromETHAmountUSD, - token: ethToken, - loading: fromETHLoading, - setLoading: setFromETHLoading, - error: fromEthBalanceError, - }); - - const fromUSDC = useValue({ - balance: fromUSDCBalanceString, - balanceResponse: fromUSDCResponse, - amount: fromUSDCAmount, - setAmount: setFromUSDCAmount, - amountUSD: fromUSDCAmountUSD, - setAmountUSD: setFromUSDCAmountUSD, - token: usdcToken, - loading: fromUSDCLoading, - setLoading: setFromUSDCLoading, - error: fromUSDCBalanceError, - }); - - const from = useValue({ - balance: fromBalanceString, - balanceResponse: fromResponse, - amount: fromAmount, - setAmount: setFromAmount, - amountUSD: fromAmountUSD, - setAmountUSD: setFromAmountUSD, - token: fromToken, - loading: fromLoading, - setLoading: setFromLoading, - error: fromBalanceError, - }); + toBalanceString: balance, + toTokenBalanceError: error, + toTokenResponse: balanceResponse, + } = useSwapBalances({ address, fromToken: token, toToken }); const to = useValue({ - balance: toBalanceString, - balanceResponse: toTokenResponse, + balance, + balanceResponse, amount: toAmount, + setAmount: setToAmount, amountUSD: toAmountUSD, setAmountUSD: setToAmountUSD, - setAmount: setToAmount, token: toToken, loading: toLoading, setLoading: setToLoading, - error: toTokenBalanceError, + error, }); - return { fromETH, from, fromUSDC, to }; + return { fromETH, fromUSDC, from, to }; }; diff --git a/src/token/constants.ts b/src/token/constants.ts index 01eed2570f..21c1852e76 100644 --- a/src/token/constants.ts +++ b/src/token/constants.ts @@ -20,3 +20,23 @@ export const usdcToken: Token = { 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/44/2b/442b80bd16af0c0d9b22e03a16753823fe826e5bfd457292b55fa0ba8c1ba213-ZWUzYjJmZGUtMDYxNy00NDcyLTg0NjQtMWI4OGEwYjBiODE2', chainId: base.id, }; + +export const degenToken: Token = { + name: 'DEGEN', + address: '0x4ed4e862860bed51a9570b96d89af5e1b0efefed', + symbol: 'DEGEN', + decimals: 18, + image: + 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/3b/bf/3bbf118b5e6dc2f9e7fc607a6e7526647b4ba8f0bea87125f971446d57b296d2-MDNmNjY0MmEtNGFiZi00N2I0LWIwMTItMDUyMzg2ZDZhMWNm', + chainId: base.id, +}; + +export const daiToken: Token = { + name: 'DAI', + address: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', + symbol: 'DAI', + decimals: 18, + image: + 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/92/13/9213e31b84c98a693f4c624580fdbe6e4c1cb550efbba15aa9ea68fd25ffb90c-ZTE1NmNjMGUtZGVkYi00ZDliLWI2N2QtNTY2ZWRjMmYwZmMw', + chainId: base.id, +}; From 46109b46b1ad6500c312b040219eca732be4e519 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Fri, 13 Dec 2024 15:27:40 -0800 Subject: [PATCH 16/16] fix lint and formatting --- src/swap/hooks/useSwapLiteTokens.test.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/swap/hooks/useSwapLiteTokens.test.ts b/src/swap/hooks/useSwapLiteTokens.test.ts index 402527c2a3..7b8280f6f8 100644 --- a/src/swap/hooks/useSwapLiteTokens.test.ts +++ b/src/swap/hooks/useSwapLiteTokens.test.ts @@ -3,15 +3,15 @@ import { base } from 'viem/chains'; import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; import { useValue } from '../../core-react/internal/hooks/useValue'; import type { Token } from '../../token'; -import { useSwapBalances } from './useSwapBalances'; -import { useSwapLiteTokens } from './useSwapLiteTokens'; -import { useSwapLiteToken } from './useSwapLiteToken'; import { daiToken, degenToken, ethToken, usdcToken, } from '../../token/constants'; +import { useSwapBalances } from './useSwapBalances'; +import { useSwapLiteToken } from './useSwapLiteToken'; +import { useSwapLiteTokens } from './useSwapLiteTokens'; vi.mock('./useSwapLiteToken'); vi.mock('./useSwapBalances'); @@ -76,9 +76,13 @@ const address = '0x123'; describe('useSwapLiteTokens', () => { beforeEach(() => { vi.clearAllMocks(); - (useSwapLiteToken as Mock).mockImplementation((toToken, fromToken) => { - if (fromToken === ethToken) return mockFromETH; - if (fromToken === usdcToken) return mockFromUSDC; + (useSwapLiteToken as Mock).mockImplementation((_toToken, fromToken) => { + if (fromToken === ethToken) { + return mockFromETH; + } + if (fromToken === usdcToken) { + return mockFromUSDC; + } return mockFrom; });