import { describe, it, expect, beforeEach, vi } from 'vitest' import { TronTransactionService } from './tron-transaction-service' import type { TronProvider } from '../../tron-discovery' import type { TronNativeTransferRequest, TronTRC20TransferRequest } from '@meshconnect/uwc-types' describe('TronTransactionService', () => { let service: TronTransactionService let mockProvider: TronProvider beforeEach(() => { vi.clearAllMocks() service = new TronTransactionService() mockProvider = { ready: true, request: vi.fn(), tronWeb: { defaultAddress: { base58: 'TFromAddr', hex: '0x1234' }, trx: { sign: vi.fn().mockResolvedValue({ txID: 'signed-tx', signature: [] }), signMessageV2: vi.fn(), sendRawTransaction: vi .fn() .mockResolvedValue({ result: true, txid: '0xresulthash' }) }, transactionBuilder: { sendTrx: vi .fn() .mockResolvedValue({ txID: 'unsigned-tx', raw_data: {} }), triggerSmartContract: vi.fn().mockResolvedValue({ transaction: { txID: 'trc20-unsigned', raw_data: {} }, result: { result: true } }) }, toHex: vi.fn() } } as any }) describe('sendTransaction routing', () => { it('should route native transfer (has amount, no contractAddress)', async () => { const request: TronNativeTransferRequest = { from: 'TFromAddr', to: 'TToAddr', amount: 1000000 } const result = await service.sendTransaction(request, mockProvider) expect(mockProvider.tronWeb.transactionBuilder.sendTrx).toHaveBeenCalled() expect(result).toBe('0xresulthash') }) it('should route TRC20 transfer (has contractAddress)', async () => { const request: TronTRC20TransferRequest = { from: 'TFromAddr', to: 'TToAddr', amount: 500, contractAddress: 'TContractAddr' } const result = await service.sendTransaction(request, mockProvider) expect( mockProvider.tronWeb.transactionBuilder.triggerSmartContract ).toHaveBeenCalled() expect(result).toBe('0xresulthash') }) it('should throw error for invalid request type', async () => { const invalidRequest = { from: 'TFromAddr' } as any await expect( service.sendTransaction(invalidRequest, mockProvider) ).rejects.toThrow('Invalid Tron transaction request type') }) }) describe('sendNativeTransaction', () => { it('should build, sign, and broadcast native transaction', async () => { const request: TronNativeTransferRequest = { from: 'TFromAddr', to: 'TToAddr', amount: 2000000 } const result = await service.sendTransaction(request, mockProvider) // Verify build expect( mockProvider.tronWeb.transactionBuilder.sendTrx ).toHaveBeenCalledWith('TToAddr', 2000000, 'TFromAddr') // Verify sign expect(mockProvider.tronWeb.trx.sign).toHaveBeenCalledWith({ txID: 'unsigned-tx', raw_data: {} }) // Verify broadcast expect(mockProvider.tronWeb.trx.sendRawTransaction).toHaveBeenCalledWith({ txID: 'signed-tx', signature: [] }) expect(result).toBe('0xresulthash') }) it('should throw when broadcast result is false', async () => { vi.mocked(mockProvider.tronWeb.trx.sendRawTransaction).mockResolvedValue({ result: false, message: 'Broadcast failed' }) const request: TronNativeTransferRequest = { from: 'TFromAddr', to: 'TToAddr', amount: 1000000 } await expect( service.sendTransaction(request, mockProvider) ).rejects.toThrow() }) it('should throw when sign fails', async () => { vi.mocked(mockProvider.tronWeb.trx.sign).mockRejectedValue( new Error('User rejected signing') ) const request: TronNativeTransferRequest = { from: 'TFromAddr', to: 'TToAddr', amount: 1000000 } await expect( service.sendTransaction(request, mockProvider) ).rejects.toThrow() }) it('should throw when build fails', async () => { vi.mocked( mockProvider.tronWeb.transactionBuilder.sendTrx ).mockRejectedValue(new Error('Insufficient balance')) const request: TronNativeTransferRequest = { from: 'TFromAddr', to: 'TToAddr', amount: 999999999999 } await expect( service.sendTransaction(request, mockProvider) ).rejects.toThrow() }) }) describe('sendTRC20Transaction', () => { it('should build, sign, and broadcast TRC20 transaction', async () => { const request: TronTRC20TransferRequest = { from: 'TFromAddr', to: 'TToAddr', amount: 100000, contractAddress: 'TUSDTContract' } const result = await service.sendTransaction(request, mockProvider) // Verify build expect( mockProvider.tronWeb.transactionBuilder.triggerSmartContract ).toHaveBeenCalledWith( 'TUSDTContract', 'transfer(address,uint256)', {}, [ { type: 'address', value: 'TToAddr' }, // uint256 is passed as a string to preserve precision for large amounts { type: 'uint256', value: '100000' } ], 'TFromAddr' ) // Verify sign expect(mockProvider.tronWeb.trx.sign).toHaveBeenCalledWith({ txID: 'trc20-unsigned', raw_data: {} }) // Verify broadcast expect(mockProvider.tronWeb.trx.sendRawTransaction).toHaveBeenCalled() expect(result).toBe('0xresulthash') }) it('should throw when TRC20 build result is false', async () => { vi.mocked( mockProvider.tronWeb.transactionBuilder.triggerSmartContract ).mockResolvedValue({ transaction: {}, result: { result: false } }) const request: TronTRC20TransferRequest = { from: 'TFromAddr', to: 'TToAddr', amount: 100, contractAddress: 'TBadContract' } await expect( service.sendTransaction(request, mockProvider) ).rejects.toThrow() }) it('should throw when TRC20 broadcast fails', async () => { vi.mocked(mockProvider.tronWeb.trx.sendRawTransaction).mockResolvedValue({ result: false, message: 'Contract execution failed' }) const request: TronTRC20TransferRequest = { from: 'TFromAddr', to: 'TToAddr', amount: 100, contractAddress: 'TContractAddr' } await expect( service.sendTransaction(request, mockProvider) ).rejects.toThrow() }) it('should throw when TRC20 sign is rejected', async () => { vi.mocked(mockProvider.tronWeb.trx.sign).mockRejectedValue( new Error('User cancelled') ) const request: TronTRC20TransferRequest = { from: 'TFromAddr', to: 'TToAddr', amount: 100, contractAddress: 'TContractAddr' } await expect( service.sendTransaction(request, mockProvider) ).rejects.toThrow() }) }) describe('error handling', () => { it('should handle user rejection errors', async () => { vi.mocked(mockProvider.tronWeb.trx.sign).mockRejectedValue({ code: 4001, message: 'User rejected the request' }) const request: TronNativeTransferRequest = { from: 'TFromAddr', to: 'TToAddr', amount: 1000000 } await expect( service.sendTransaction(request, mockProvider) ).rejects.toThrow() }) }) })