UNPKG

@ledgerhq/coin-hedera

Version:
255 lines (213 loc) 8.11 kB
import cvsApi from "@ledgerhq/live-countervalues/api/index"; import BigNumber from "bignumber.js"; import { DEFAULT_GAS_LIMIT, DEFAULT_GAS_PRICE_TINYBARS, DEFAULT_TINYBAR_FEE, ESTIMATED_FEE_SAFETY_RATE, ESTIMATED_GAS_SAFETY_RATE, HEDERA_OPERATION_TYPES, HEDERA_TRANSACTION_MODES, TINYBAR_SCALE, } from "../constants"; import { apiClient } from "../network/api"; import { getMockedAccount } from "../test/fixtures/account.fixture"; import { getMockedERC20TokenCurrency } from "../test/fixtures/currency.fixture"; import { estimateFees } from "./estimateFees"; import { getCurrencyToUSDRate, toEVMAddress } from "./utils"; jest.mock("@ledgerhq/live-countervalues/api/index"); jest.mock("../network/api"); describe("getEstimatedFees", () => { const mockedAccount = getMockedAccount(); const mockedTokenCurrencyERC20 = getMockedERC20TokenCurrency(); const senderAddress = "0.0.12345"; const recipientAddress = "0.0.67890"; beforeEach(() => { jest.clearAllMocks(); // reset LRU cache to make sure all tests receive correct mocks from mockedFetchLatest getCurrencyToUSDRate.clear(mockedAccount.currency.ticker); }); it("returns estimated fee based on USD rate for CryptoTransfer", async () => { const usdRate = 1; // 1 HBAR = 1 USD (cvsApi.fetchLatest as jest.Mock).mockResolvedValueOnce([usdRate]); const result = await estimateFees({ currency: mockedAccount.currency, operationType: HEDERA_OPERATION_TYPES.CryptoTransfer, }); const baseFeeTinybar = 0.0001 * 10 ** TINYBAR_SCALE; const expectedTinybars = new BigNumber(baseFeeTinybar) .div(usdRate) .integerValue(BigNumber.ROUND_CEIL) .multipliedBy(ESTIMATED_FEE_SAFETY_RATE); expect(result).toMatchObject({ tinybars: expectedTinybars, }); }); it("returns estimated fee based on USD rate for TokenTransfer", async () => { const usdRate = 0.5; // 1 HBAR = 0.5 USD (cvsApi.fetchLatest as jest.Mock).mockResolvedValueOnce([usdRate]); const result = await estimateFees({ currency: mockedAccount.currency, operationType: HEDERA_OPERATION_TYPES.TokenTransfer, }); const baseFeeTinybar = 0.001 * 10 ** TINYBAR_SCALE; const expectedTinybars = new BigNumber(baseFeeTinybar) .div(usdRate) .integerValue(BigNumber.ROUND_CEIL) .multipliedBy(ESTIMATED_FEE_SAFETY_RATE); expect(result).toMatchObject({ tinybars: expectedTinybars, }); }); it("returns estimated fee based on USD rate for TokenAssociate", async () => { const usdRate = 2; // 1 HBAR = 2 USD (cvsApi.fetchLatest as jest.Mock).mockResolvedValueOnce([usdRate]); const result = await estimateFees({ currency: mockedAccount.currency, operationType: HEDERA_OPERATION_TYPES.TokenAssociate, }); const baseFeeTinybar = 0.05 * 10 ** TINYBAR_SCALE; const expectedTinybars = new BigNumber(baseFeeTinybar) .div(usdRate) .integerValue(BigNumber.ROUND_CEIL) .multipliedBy(ESTIMATED_FEE_SAFETY_RATE); expect(result).toMatchObject({ tinybars: expectedTinybars, }); }); it("returns estimated fee based on gas for ContractCall", async () => { const estimatedGasLimit = new BigNumber(50000); const gasPriceTinybars = new BigNumber(900); const transferAmount = BigInt(1000000); (apiClient.getAccount as jest.Mock).mockImplementation(address => ({ address, evm_address: "0x0000000000000000000000000000000000012345", })); (apiClient.getNetworkFees as jest.Mock).mockResolvedValueOnce({ fees: [ { transaction_type: "ContractCall", gas: gasPriceTinybars.toNumber(), }, ], }); (apiClient.estimateContractCallGas as jest.Mock).mockResolvedValueOnce(estimatedGasLimit); const result = await estimateFees({ operationType: HEDERA_OPERATION_TYPES.ContractCall, txIntent: { intentType: "transaction", type: HEDERA_TRANSACTION_MODES.Send, sender: senderAddress, recipient: recipientAddress, amount: transferAmount, asset: { type: "erc20", assetReference: mockedTokenCurrencyERC20.contractAddress, }, }, }); const expectedGas = estimatedGasLimit .multipliedBy(ESTIMATED_GAS_SAFETY_RATE) .integerValue(BigNumber.ROUND_CEIL); const expectedTinybars = expectedGas .multipliedBy(gasPriceTinybars) .integerValue(BigNumber.ROUND_CEIL); expect(apiClient.getAccount).toHaveBeenCalledTimes(2); expect(apiClient.getNetworkFees).toHaveBeenCalledTimes(1); expect(apiClient.estimateContractCallGas).toHaveBeenCalledTimes(1); expect(apiClient.estimateContractCallGas).toHaveBeenCalledWith( await toEVMAddress(senderAddress), await toEVMAddress(recipientAddress), mockedTokenCurrencyERC20.contractAddress, transferAmount, ); expect(result).toMatchObject({ tinybars: expectedTinybars, gas: expectedGas, }); }); it("falls back to default gas values when getNetworkFees fail", async () => { const transferAmount = BigInt(1000000); (apiClient.getNetworkFees as jest.Mock).mockRejectedValueOnce(new Error("Network error")); const result = await estimateFees({ operationType: HEDERA_OPERATION_TYPES.ContractCall, txIntent: { intentType: "transaction", type: HEDERA_TRANSACTION_MODES.Send, sender: senderAddress, recipient: recipientAddress, amount: transferAmount, asset: { type: "erc20", assetReference: mockedTokenCurrencyERC20.contractAddress, }, }, }); const expectedGas = DEFAULT_GAS_LIMIT; const expectedTinybars = new BigNumber(expectedGas) .multipliedBy(DEFAULT_GAS_PRICE_TINYBARS) .integerValue(BigNumber.ROUND_CEIL); expect(result).toMatchObject({ tinybars: expectedTinybars, gas: expectedGas, }); }); it("falls back to default gas values when estimateContractCallGas fail", async () => { const transferAmount = BigInt(1000000); (apiClient.getNetworkFees as jest.Mock).mockResolvedValueOnce({ fees: [], }); (apiClient.estimateContractCallGas as jest.Mock).mockRejectedValueOnce( new Error("Network error"), ); const result = await estimateFees({ operationType: HEDERA_OPERATION_TYPES.ContractCall, txIntent: { intentType: "transaction", type: HEDERA_TRANSACTION_MODES.Send, sender: senderAddress, recipient: recipientAddress, amount: transferAmount, asset: { type: "erc20", assetReference: mockedTokenCurrencyERC20.contractAddress, }, }, }); const expectedGas = DEFAULT_GAS_LIMIT; const expectedTinybars = new BigNumber(expectedGas) .multipliedBy(DEFAULT_GAS_PRICE_TINYBARS) .integerValue(BigNumber.ROUND_CEIL); expect(result).toMatchObject({ tinybars: expectedTinybars, gas: expectedGas, }); }); it("falls back to default estimate when cvs api returns null", async () => { const usdRate = null; (cvsApi.fetchLatest as jest.Mock).mockResolvedValueOnce([usdRate]); const result = await estimateFees({ currency: mockedAccount.currency, operationType: HEDERA_OPERATION_TYPES.CryptoTransfer, }); const expectedTinybars = new BigNumber(DEFAULT_TINYBAR_FEE).multipliedBy( ESTIMATED_FEE_SAFETY_RATE, ); expect(result).toMatchObject({ tinybars: expectedTinybars, }); }); it("falls back to default estimate on cvs api failure", async () => { (cvsApi.fetchLatest as jest.Mock).mockRejectedValueOnce(new Error("Network error")); const result = await estimateFees({ currency: mockedAccount.currency, operationType: HEDERA_OPERATION_TYPES.CryptoTransfer, }); const expectedTinybars = new BigNumber(DEFAULT_TINYBAR_FEE).multipliedBy( ESTIMATED_FEE_SAFETY_RATE, ); expect(result).toMatchObject({ tinybars: expectedTinybars, }); }); });