UNPKG

@ledgerhq/coin-stellar

Version:
167 lines (142 loc) 5.75 kB
// SPDX-FileCopyrightText: © 2026 LEDGER SAS // SPDX-License-Identifier: Apache-2.0 import { BalanceOptions, TransactionIntent } from '@ledgerhq/coin-module-framework/api/types' import { InvalidParameterError } from '@ledgerhq/errors' import { StellarMemo } from '../types' import { createApi, envelopeFromAnyXDR } from './index' const mockGetOperations = jest.fn() jest.mock('../logic/listOperations', () => ({ listOperations: () => mockGetOperations(), })) const CUSTOM_FEES = 300n const estimateFeesMock = jest.fn(() => CUSTOM_FEES) jest.mock('../logic/estimateFees', () => ({ estimateFees: () => estimateFeesMock(), })) const logicCraftTransactionMock = jest.fn((_account: unknown, _transaction: { fee: bigint }) => { return { xdr: undefined } }) jest.mock('../logic/craftTransaction', () => ({ craftTransaction: (account: unknown, transaction: { fee: bigint }) => logicCraftTransactionMock(account, transaction), })) const api = createApi({ explorer: { url: 'explorer.com', fetchLimit: 200, }, useStaticFees: true, enableNetworkLogs: false, }) const fromGenesisMinHeight = 0 const fromGenesisOrder = 'asc' as const describe('operations', () => { beforeEach(() => { jest.clearAllMocks() }) const mockOperation = { asset: { type: 'native' }, tx: { hash: 'e035a56c32003e3b0e4c9c5499b0750d71d98233ae6ae94323ff0a458b05a30b', fees: 0.0291, block: { hash: 'hash', time: new Date('2024-03-20T10:00:00Z'), height: 10, }, date: new Date('2024-03-20T10:00:00Z'), }, type: 'Operation', value: 200, senders: ['addr'], recipients: ['recipient'], } it('should return 0 operations for a valid account', async () => { mockGetOperations.mockResolvedValue({ items: [], next: '' }) // When const operations = await api.listOperations('addr', { minHeight: fromGenesisMinHeight, order: fromGenesisOrder, }) // Then expect(operations).toEqual({ items: [], next: undefined }) expect(mockGetOperations).toHaveBeenCalledTimes(1) }) it('should return 1 operation for a valid account', async () => { mockGetOperations.mockResolvedValue({ items: [mockOperation], next: '' }) // When const operations = await api.listOperations('addr', { minHeight: fromGenesisMinHeight, order: fromGenesisOrder, }) // Then expect(operations).toEqual({ items: [mockOperation], next: undefined }) expect(mockGetOperations).toHaveBeenCalledTimes(1) }) it('should return cursor for caller-driven pagination', async () => { mockGetOperations.mockResolvedValueOnce({ items: [mockOperation], next: 'cursor-abc' }) // When const operations = await api.listOperations('addr', { minHeight: fromGenesisMinHeight, order: fromGenesisOrder, }) expect(operations).toEqual({ items: [mockOperation], next: 'cursor-abc' }) expect(mockGetOperations).toHaveBeenCalledTimes(1) }) }) describe('Testing craftTransaction function', () => { beforeEach(() => { estimateFeesMock.mockClear() logicCraftTransactionMock.mockClear() }) it('should use estimated fees when user does not provide them for crafting a transaction', async () => { await api.craftTransaction({ asset: {} } as TransactionIntent<StellarMemo>) expect(estimateFeesMock).toHaveBeenCalledTimes(1) expect(logicCraftTransactionMock).toHaveBeenCalledWith( expect.any(Object), expect.objectContaining({ fee: CUSTOM_FEES }) ) }) it.each([[1n], [50n], [99n]])( 'should use custom user fees when user provide them for crafting a transaction', async (fees: bigint) => { await api.craftTransaction({ asset: {} } as TransactionIntent<StellarMemo>, { value: fees }) expect(estimateFeesMock).toHaveBeenCalledTimes(0) expect(logicCraftTransactionMock).toHaveBeenCalledWith( expect.any(Object), expect.objectContaining({ fee: fees }) ) } ) }) describe('Testing transaction loading functions', () => { it('should deserialize a transaction as expected', async () => { const transactionPayloadXDR = 'esM5l1ROMXXSZr0CJDmyLNsWUIwBFj8m5csqPhBFqXkAAAACAAAAAEFMhHdla/OhHE2CYrF1VVPnLgBThGuzpNFZyYMh' + '8L6XAAAAZAAAJ/cAAAkYAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAABHRlc3QAAAABAAAAAAAAAAAAAAAA/QIumXyU' + '+Nq3dDZfGCXjgxYI7uvPElz8zGb0gN+vWD8AAAAAAA9CQAAAAAA=' const transactionEnvelopeXDR = 'AAAAAgAAAABBTIR3ZWvzoRxNgmKxdVVT5y4AU4Rrs6TRWcmDIfC+lwAAAGQAACf3AAAJGAAAAAEAAAAAAAAAAAAAAAAA' + 'AAAAAAAAAQAAAAR0ZXN0AAAAAQAAAAAAAAAAAAAAAP0CLpl8lPjat3Q2Xxgl44MWCO7rzxJc/Mxm9IDfr1g/AAAAAAAP' + 'QkAAAAAAAAAAAA==' const txFromSignaturePayload = envelopeFromAnyXDR(transactionPayloadXDR, 'base64') const txFromEnvelope = envelopeFromAnyXDR(transactionEnvelopeXDR, 'base64') expect(txFromEnvelope).toEqual(txFromSignaturePayload) expect(txFromEnvelope.toXDR('base64')).toEqual(transactionEnvelopeXDR) expect(txFromSignaturePayload.toXDR('base64')).toEqual(transactionEnvelopeXDR) }) it('throw expected error when deserializing an invalid transaction', async () => { expect(() => envelopeFromAnyXDR('lulz', 'base64')).toThrow( 'Failed decoding transaction as an envelope (TypeError: XDR Read Error: attempt to read outside the boundary of' + ' the buffer) or as a signature base: (TypeError: XDR Read Error: attempt to read outside the boundary of the' + ' buffer)' ) }) }) describe('getBalance', () => { it('should throw an exception when options is provided', async () => { await expect(api.getBalance('random address', {} as unknown as BalanceOptions)).rejects.toThrow( InvalidParameterError ) }) })