import { act, renderHook } from '@testing-library/react-native' import BigNumber from 'bignumber.js' import { FetchMock } from 'jest-fetch-mock/types' import React from 'react' import { Provider } from 'react-redux' import { usePrepareEnterAmountTransactionsCallback } from 'src/earn/hooks' import { RawShortcutTransaction } from 'src/positions/slice' import { ShortcutStatus } from 'src/positions/types' import { TokenBalance } from 'src/tokens/slice' import { NetworkId } from 'src/transactions/types' import { prepareTransactions } from 'src/viem/prepareTransactions' import networkConfig from 'src/web3/networkConfig' import { createMockStore } from 'test/utils' import { mockAaveArbUsdcAddress, mockAccount, mockArbArbAddress, mockArbEthTokenId, mockArbUsdcTokenId, mockEarnPositions, mockPositions, mockShortcuts, mockTokenBalances, } from 'test/values' import { parseGwei } from 'viem' const mockFetch = fetch as FetchMock const gas = BigInt(100_000) const maxFeePerGas = parseGwei('5') const _baseFeePerGas = parseGwei('1') const transactions = [ { from: '0x2b8441ef13333ffa955c9ea5ab5b3692da95260d', networkId: NetworkId['celo-alfajores'], data: '0x3d18b912', to: '0xda7f463c27ec862cfbf2369f3f74c364d050d93f', } as const, ] const rewardId = 'claim-reward-0xda7f463c27ec862cfbf2369f3f74c364d050d93f-0.010209368244703615' jest.mock('src/viem/prepareTransactions') function getMockStoreWithShortcutStatus( status: ShortcutStatus, transactions: RawShortcutTransaction[] ) { return createMockStore({ positions: { positions: mockPositions, shortcuts: mockShortcuts, triggeredShortcutsStatus: { [rewardId]: { status, transactions, appName: 'some app name', appImage: 'https://some.app/image.png', }, }, }, }) } const mockFeeCurrencies: TokenBalance[] = [ { ...mockTokenBalances[mockArbEthTokenId], isNative: true, balance: new BigNumber(1), priceUsd: new BigNumber(1500), lastKnownPriceUsd: new BigNumber(1500), }, ] const mockRefreshArgs = { pool: mockEarnPositions[0], hooksApiUrl: networkConfig.hooksApiUrl, } const mockResponseBody = { message: 'OK', data: { transactions: [ { networkId: NetworkId['arbitrum-sepolia'], from: '0xfrom', to: '0xto', data: '0xdata', }, ], }, } const mockSwapDepositResponseBody = { message: 'OK', data: { transactions: [ { networkId: NetworkId['arbitrum-sepolia'], from: '0xfrom', to: '0xto', data: '0xdata', }, { networkId: NetworkId['arbitrum-sepolia'], from: '0xfrom', to: '0xto', data: '0xdata', value: '0', gas: '2788000', estimatedGasUse: '892551', }, ], dataProps: { swapTransaction: { chainId: 44787, buyAmount: '994820', sellAmount: '1785876928077378476', buyTokenAddress: mockArbArbAddress, sellTokenAddress: mockAaveArbUsdcAddress, price: '0.557', guaranteedPrice: '0.55', estimatedPriceImpact: '0.0', gas: '1800000', gasPrice: '10000000', to: '0x0000000000000000000000000000000000000123', value: '0', data: '0x0', from: mockAccount, allowanceTarget: '0x0000000000000000000000000000000000000123', estimatedGasUse: '971972', swapType: 'same-chain', appFeePercentageIncludedInPrice: '0.6', simulationStatus: 'success', }, }, }, } const expectedPrepareTransactionsResult = { prepareTransactionsResult: { type: 'possible', transactions: [ { from: '0xfrom', to: '0xto', data: '0xdata', gas, maxFeePerGas, _baseFeePerGas, }, ], feeCurrency: mockFeeCurrencies[0], }, } const expectedSwapDepositPrepareTransactionsResult = { prepareTransactionsResult: { feeCurrency: mockFeeCurrencies[0], transactions: [ { gas, maxFeePerGas, _baseFeePerGas, _estimatedGasUse: undefined, data: '0xdata', from: '0xfrom', to: '0xto', value: undefined, }, { gas, maxFeePerGas, _baseFeePerGas, _estimatedGasUse: BigInt(892551), data: '0xdata', from: '0xfrom', to: '0xto', value: BigInt(0), }, ], type: 'possible', }, swapTransaction: { allowanceTarget: '0x0000000000000000000000000000000000000123', appFeePercentageIncludedInPrice: '0.6', buyAmount: '994820', buyTokenAddress: mockArbArbAddress, chainId: 44787, data: '0x0', estimatedGasUse: '971972', estimatedPriceImpact: '0.0', from: mockAccount, gas: '1800000', gasPrice: '10000000', guaranteedPrice: '0.55', price: '0.557', sellAmount: '1785876928077378476', sellTokenAddress: mockAaveArbUsdcAddress, simulationStatus: 'success', swapType: 'same-chain', to: '0x0000000000000000000000000000000000000123', value: '0', }, } describe('usePrepareEnterAmountTransactionsCallback', () => { beforeEach(() => { jest.clearAllMocks() jest.mocked(prepareTransactions).mockImplementation(async ({ baseTransactions }) => ({ transactions: baseTransactions.map((tx) => ({ ...tx, gas, maxFeePerGas, _baseFeePerGas, })), type: 'possible' as const, feeCurrency: mockFeeCurrencies[0], })) }) it.each([ { mode: 'withdraw', title: 'calls correct transaction preparation for withdraw', mockResponseBody: mockResponseBody, expectedPrepareTransactionsResult: expectedPrepareTransactionsResult, mockRefreshArgs: mockRefreshArgs, }, { mode: 'withdraw', title: 'calls correct transaction preparation for partial withdraw', mockResponseBody: mockResponseBody, expectedPrepareTransactionsResult: expectedPrepareTransactionsResult, mockRefreshArgs: mockRefreshArgs, }, { mode: 'deposit', title: 'calls correct transaction preparation for deposit', mockResponseBody: mockResponseBody, expectedPrepareTransactionsResult: expectedPrepareTransactionsResult, mockRefreshArgs: { ...mockRefreshArgs, token: mockTokenBalances[mockArbUsdcTokenId], }, }, { mode: 'swap-deposit', title: 'calls correct transaction preparation for swap-deposit', mockResponseBody: mockSwapDepositResponseBody, expectedPrepareTransactionsResult: expectedSwapDepositPrepareTransactionsResult, mockRefreshArgs: { ...mockRefreshArgs, token: mockTokenBalances[mockArbUsdcTokenId], }, }, ] as const)( '$title', async ({ mode, mockResponseBody, expectedPrepareTransactionsResult, mockRefreshArgs }) => { const { result } = renderHook(() => usePrepareEnterAmountTransactionsCallback(mode), { wrapper: (component) => ( {component?.children ? component.children : component} ), }) mockFetch.mockResponse(JSON.stringify(mockResponseBody), { status: 200, }) await act(async () => { await result.current.refreshPreparedTransactions(mockRefreshArgs) }) // Validate the initial state and functions returned by the hook expect(result.current).toEqual({ prepareTransactionsResult: expectedPrepareTransactionsResult, isPreparingTransactions: false, prepareTransactionError: undefined, refreshPreparedTransactions: expect.any(Function), clearPreparedTransactions: expect.any(Function), }) } ) it('errors when unable to trigger shortcut error response from hooks API', async () => { const { result } = renderHook(() => usePrepareEnterAmountTransactionsCallback('withdraw'), { wrapper: (component) => ( {component?.children ? component.children : component} ), }) mockFetch.mockResponse(JSON.stringify({}), { status: 500, }) await expect(result.current.refreshPreparedTransactions(mockRefreshArgs)).rejects.toEqual( new Error('Unable to trigger shortcut: 500 Internal Server Error') ) }) })