diff --git a/libs/ledger-live-common/src/families/aptos/bridge/js.test.ts b/libs/ledger-live-common/src/families/aptos/bridge/js.test.ts new file mode 100644 index 000000000000..ce24ff1fe361 --- /dev/null +++ b/libs/ledger-live-common/src/families/aptos/bridge/js.test.ts @@ -0,0 +1,106 @@ +import BigNumber from "bignumber.js"; +import bridge from "./js"; +import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; + +describe("Aptos bridge interface ", () => { + describe("currencyBridge", () => { + it("should have a preload method that returns a promise", async () => { + const cryptoCurrency = getCryptoCurrencyById("aptos"); + const result = bridge.currencyBridge.preload(cryptoCurrency); + expect(result).toBeInstanceOf(Promise); + await expect(result).resolves.toEqual({}); + }); + + it("should have a hydrate method that is a function", () => { + expect(bridge.currencyBridge.hydrate).toBeDefined(); + expect(typeof bridge.currencyBridge.hydrate).toBe("function"); + const cryptoCurrency = getCryptoCurrencyById("aptos"); + const result = bridge.currencyBridge.hydrate({}, cryptoCurrency); + expect(result).toBeUndefined(); + }); + + it("should have a scanAccounts method that is a function", () => { + expect(bridge.currencyBridge.scanAccounts).toBeDefined(); + expect(typeof bridge.currencyBridge.scanAccounts).toBe("function"); + const cryptoCurrency = getCryptoCurrencyById("aptos"); + const deviceId = "test-device"; + const result = bridge.currencyBridge.scanAccounts({ + currency: cryptoCurrency, + deviceId, + syncConfig: { paginationConfig: {} }, + }); + expect(result).toBeDefined(); + }); + }); + + describe("accountBridge ", () => { + it("should contain all methods", () => { + expect(bridge.accountBridge.estimateMaxSpendable).toBeDefined(); + expect(typeof bridge.accountBridge.estimateMaxSpendable).toBe("function"); + expect(bridge.accountBridge.createTransaction).toBeDefined(); + expect(typeof bridge.accountBridge.createTransaction).toBe("function"); + expect(bridge.accountBridge.updateTransaction).toBeDefined(); + expect(typeof bridge.accountBridge.updateTransaction).toBe("function"); + expect(bridge.accountBridge.getTransactionStatus).toBeDefined(); + expect(typeof bridge.accountBridge.getTransactionStatus).toBe("function"); + expect(bridge.accountBridge.prepareTransaction).toBeDefined(); + expect(typeof bridge.accountBridge.prepareTransaction).toBe("function"); + expect(bridge.accountBridge.sync).toBeDefined(); + expect(typeof bridge.accountBridge.sync).toBe("function"); + expect(bridge.accountBridge.receive).toBeDefined(); + expect(typeof bridge.accountBridge.receive).toBe("function"); + expect(bridge.accountBridge.signOperation).toBeDefined(); + expect(typeof bridge.accountBridge.signOperation).toBe("function"); + expect(bridge.accountBridge.broadcast).toBeDefined(); + expect(typeof bridge.accountBridge.broadcast).toBe("function"); + }); + }); + describe("updateTransaction", () => { + it("should update the transaction with the given patch", () => { + const initialTransaction = { + amount: new BigNumber(100), + recipient: "address1", + mode: "send", + family: "aptos" as const, + options: { maxGasAmount: "", gasUnitPrice: "" }, + estimate: { maxGasAmount: "", gasUnitPrice: "" }, + firstEmulation: true, + }; + const patch = { amount: new BigNumber(200) }; + const updatedTransaction = bridge.accountBridge.updateTransaction(initialTransaction, patch); + expect(updatedTransaction).toEqual({ + amount: new BigNumber(200), + recipient: "address1", + mode: "send", + family: "aptos" as const, + options: { maxGasAmount: "", gasUnitPrice: "" }, + estimate: { maxGasAmount: "", gasUnitPrice: "" }, + firstEmulation: true, + }); + }); + + it("should not modify the original transaction object", () => { + const initialTransaction = { + amount: new BigNumber(100), + recipient: "address1", + mode: "send", + family: "aptos" as const, + options: { maxGasAmount: "", gasUnitPrice: "" }, + estimate: { maxGasAmount: "", gasUnitPrice: "" }, + firstEmulation: true, + }; + const patch = { amount: new BigNumber(200) }; + const updatedTransaction = bridge.accountBridge.updateTransaction(initialTransaction, patch); + expect(initialTransaction).toEqual({ + amount: new BigNumber(100), + recipient: "address1", + mode: "send", + family: "aptos" as const, + options: { maxGasAmount: "", gasUnitPrice: "" }, + estimate: { maxGasAmount: "", gasUnitPrice: "" }, + firstEmulation: true, + }); + expect(updatedTransaction).not.toBe(initialTransaction); + }); + }); +}); diff --git a/libs/ledger-live-common/src/families/aptos/bridge/js.ts b/libs/ledger-live-common/src/families/aptos/bridge/js.ts index d2a7eae3f379..4cd4d177cbb1 100644 --- a/libs/ledger-live-common/src/families/aptos/bridge/js.ts +++ b/libs/ledger-live-common/src/families/aptos/bridge/js.ts @@ -11,8 +11,6 @@ import estimateMaxSpendable from "../estimateMaxSpendable"; import signOperation from "../signOperation"; import broadcast from "../broadcast"; -const receive = makeAccountBridgeReceive(); - const currencyBridge: CurrencyBridge = { preload: () => Promise.resolve({}), hydrate: () => {}, @@ -24,6 +22,8 @@ const updateTransaction = (t: Transaction, patch: Partial): Transac ...patch, }); +const receive = makeAccountBridgeReceive(); + const accountBridge: AccountBridge = { estimateMaxSpendable, createTransaction, diff --git a/libs/ledger-live-common/src/families/aptos/broadcast.test.ts b/libs/ledger-live-common/src/families/aptos/broadcast.test.ts new file mode 100644 index 000000000000..54cf9907f8d5 --- /dev/null +++ b/libs/ledger-live-common/src/families/aptos/broadcast.test.ts @@ -0,0 +1,101 @@ +import broadcast from "./broadcast"; +import { AptosAPI } from "./api"; +import { patchOperationWithHash } from "./../../operation"; +import { log } from "@ledgerhq/logs"; +import type { Account, Operation, SignedOperation } from "@ledgerhq/types-live"; +import BigNumber from "bignumber.js"; + +jest.mock("./api"); +jest.mock("./../../operation"); +jest.mock("@ledgerhq/logs"); + +describe("broadcast", () => { + const mockAccount: Account = { + type: "Account", + seedIdentifier: "mockSeedIdentifier", + operationsCount: 0, + id: "mockAccountId", + currency: { + type: "CryptoCurrency", + id: "aptos", + name: "Aptos", + ticker: "APT", + units: [{ name: "APT", code: "APT", magnitude: 6 }], + managerAppName: "Aptos", + coinType: 637, + scheme: "aptos", + color: "#000000", + family: "aptos", + blockAvgTime: 5, + explorerViews: [], + }, + balance: BigNumber(1000), + spendableBalance: BigNumber(1000), + operations: [], + pendingOperations: [], + lastSyncDate: new Date(), + blockHeight: 0, + index: 0, + derivationMode: "", + freshAddress: "", + freshAddressPath: "", + used: false, + swapHistory: [], + creationDate: new Date(), + balanceHistoryCache: { + HOUR: { latestDate: 0, balances: [] }, + DAY: { latestDate: 0, balances: [] }, + WEEK: { latestDate: 0, balances: [] }, + }, + }; + + const mockOperation: Operation = { + id: "mockOperationId", + hash: "", + type: "OUT", + value: BigNumber(100), + fee: BigNumber(1), + senders: ["sender"], + recipients: ["recipient"], + blockHeight: null, + blockHash: null, + accountId: "mockAccountId", + date: new Date(), + extra: {}, + }; + + const mockSignedOperation: SignedOperation = { + operation: mockOperation, + signature: "mockSignature", + }; + + it("should broadcast the signed operation and return the patched operation", async () => { + const mockHash = "mockHash"; + (AptosAPI.prototype.broadcast as jest.Mock).mockResolvedValue(mockHash); + (patchOperationWithHash as jest.Mock).mockReturnValue({ + ...mockOperation, + hash: mockHash, + }); + + const result = await broadcast({ + signedOperation: mockSignedOperation, + account: mockAccount, + }); + + expect(AptosAPI.prototype.broadcast).toHaveBeenCalledWith("mockSignature"); + expect(patchOperationWithHash).toHaveBeenCalledWith(mockOperation, mockHash); + expect(log).toHaveBeenCalledWith("INFO", "APTOS_OP", mockOperation); + expect(result).toEqual({ ...mockOperation, hash: mockHash }); + }); + + it("should throw an error if broadcast fails", async () => { + (AptosAPI.prototype.broadcast as jest.Mock).mockRejectedValue(new Error("Broadcast failed")); + + await expect( + broadcast({ + signedOperation: mockSignedOperation, + account: mockAccount, + }), + ).rejects.toThrow("Broadcast failed"); + }); +}); diff --git a/libs/ledger-live-common/src/families/aptos/getTransactionStatus.ts b/libs/ledger-live-common/src/families/aptos/getTransactionStatus.ts index 2dbb59532b3d..469954737047 100644 --- a/libs/ledger-live-common/src/families/aptos/getTransactionStatus.ts +++ b/libs/ledger-live-common/src/families/aptos/getTransactionStatus.ts @@ -12,12 +12,12 @@ import type { Account } from "@ledgerhq/types-live"; import type { TransactionStatus } from "../..//generated/types"; import type { Transaction } from "./types"; -import { isValidAddress } from "./logic"; import { SequenceNumberTooNewError, SequenceNumberTooOldError, TransactionExpiredError, } from "./errors"; +import { AccountAddress } from "@aptos-labs/ts-sdk"; const getTransactionStatus = async (a: Account, t: Transaction): Promise => { const errors: Record = {}; @@ -44,7 +44,7 @@ const getTransactionStatus = async (a: Account, t: Transaction): Promise ({ + getCryptoCurrencyById: jest.fn(), +})); + +describe("Aptos logic ", () => { + describe("isTestnet", () => { + it("should return true for testnet currencies", () => { + expect(isTestnet("aptos_testnet")).toBe(true); + }); + + it("should return false for mainnet currencies", () => { + expect(isTestnet("aptos")).toBe(false); + }); + }); + + describe("getMaxSendBalance", () => { + it("should return the correct max send balance when amount is greater than total gas", () => { + const amount = new BigNumber(1000000); + const gas = new BigNumber(200); + const gasPrice = new BigNumber(100); + const result = getMaxSendBalance(amount, gas, gasPrice); + expect(result.isEqualTo(amount.minus(gas.multipliedBy(gasPrice)))).toBe(true); + }); + + it("should return zero when amount is less than total gas", () => { + const amount = new BigNumber(1000); + const gas = new BigNumber(200); + const gasPrice = new BigNumber(100); + const result = getMaxSendBalance(amount, gas, gasPrice); + expect(result.isEqualTo(new BigNumber(0))).toBe(true); + }); + + it("should return zero when amount is equal to total gas", () => { + const amount = new BigNumber(20000); + const gas = new BigNumber(200); + const gasPrice = new BigNumber(100); + const result = getMaxSendBalance(amount, gas, gasPrice); + expect(result.isEqualTo(new BigNumber(0))).toBe(true); + }); + + it("should handle zero amount", () => { + const amount = new BigNumber(0); + const gas = new BigNumber(200); + const gasPrice = new BigNumber(100); + const result = getMaxSendBalance(amount, gas, gasPrice); + expect(result.isEqualTo(new BigNumber(0))).toBe(true); + }); + + it("should handle zero gas and gas price", () => { + const amount = new BigNumber(1000000); + const gas = new BigNumber(0); + const gasPrice = new BigNumber(0); + const result = getMaxSendBalance(amount, gas, gasPrice); + expect(result.isEqualTo(amount)).toBe(true); + }); + }); + + describe("normalizeTransactionOptions", () => { + it("should normalize transaction options", () => { + const options: Transaction["options"] = { + maxGasAmount: "1000", + gasUnitPrice: "10", + sequenceNumber: "1", + expirationTimestampSecs: "1000000", + }; + + const result = normalizeTransactionOptions(options); + expect(result).toEqual(options); + }); + + it("should return undefined for empty values", () => { + const options: Transaction["options"] = { + maxGasAmount: "", + gasUnitPrice: "", + sequenceNumber: undefined, + expirationTimestampSecs: "1000000", + }; + + const result = normalizeTransactionOptions(options); + expect(result).toEqual({ + maxGasAmount: undefined, + gasUnitPrice: undefined, + sequenceNumber: undefined, + expirationTimestampSecs: "1000000", + }); + }); + }); + + describe("getBlankOperation", () => { + it("should return a blank operation", () => { + const tx: AptosTransaction = { + hash: "0x123", + block: { hash: "0xabc", height: 1 }, + timestamp: "1000000", + sequence_number: "1", + } as unknown as AptosTransaction; + + const id = "test-id"; + const result = getBlankOperation(tx, id); + + expect(result).toEqual({ + id: "", + hash: "0x123", + type: "", + value: new BigNumber(0), + fee: new BigNumber(0), + blockHash: "0xabc", + blockHeight: 1, + senders: [], + recipients: [], + accountId: id, + date: new Date(1000), + extra: { version: undefined }, + transactionSequenceNumber: 1, + hasFailed: false, + }); + }); + }); +}); describe("Aptos sync logic ", () => { describe("compareAddress", () => { @@ -446,4 +576,232 @@ describe("Aptos sync logic ", () => { expect(result).toEqual(new BigNumber(90).negated()); // 100 - 10 }); }); + + describe("txsToOps", () => { + it("should convert transactions to operations correctly", () => { + const address = "0x11"; + const id = "test-id"; + const txs: AptosTransaction[] = [ + { + hash: "0x123", + sender: "0x11", + gas_used: "200", + gas_unit_price: "100", + success: true, + payload: { + type: "entry_function_payload", + function: "0x1::coin::transfer", + type_arguments: [], + arguments: ["0x12", 100], + } as EntryFunctionPayloadResponse, + events: [ + { + type: "0x1::coin::WithdrawEvent", + guid: { + account_address: "0x11", + creation_number: "1", + }, + data: { + amount: "100", + }, + }, + { + type: "0x1::coin::DepositEvent", + guid: { + account_address: "0x12", + creation_number: "2", + }, + data: { + amount: "100", + }, + }, + ], + changes: [ + { + type: "write_resource", + data: { + type: APTOS_COIN_CHANGE, + data: { + withdraw_events: { + guid: { + id: { + addr: "0x11", + creation_num: "1", + }, + }, + }, + deposit_events: { + guid: { + id: { + addr: "0x12", + creation_num: "2", + }, + }, + }, + }, + }, + }, + ], + block: { hash: "0xabc", height: 1 }, + timestamp: "1000000", + sequence_number: "1", + } as unknown as AptosTransaction, + ]; + + const result = txsToOps({ address }, id, txs); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ + id: expect.any(String), + hash: "0x123", + type: DIRECTION.OUT, + value: new BigNumber(20100), + fee: new BigNumber(20000), + blockHash: "0xabc", + blockHeight: 1, + senders: ["0x11"], + recipients: ["0x12"], + accountId: id, + date: new Date(1000), + extra: { version: undefined }, + transactionSequenceNumber: 1, + hasFailed: false, + }); + }); + + it("should skip transactions without functions in payload", () => { + const address = "0x11"; + const id = "test-id"; + const txs: AptosTransaction[] = [ + { + hash: "0x123", + sender: "0x11", + gas_used: "200", + gas_unit_price: "100", + success: true, + payload: {} as EntryFunctionPayloadResponse, + // payload: { + // type: "entry_function_payload", + // function: "0x1::coin::transfer", + // type_arguments: [], + // arguments: ["0x12", 100], + // } as EntryFunctionPayloadResponse, + events: [], + changes: [], + block: { hash: "0xabc", height: 1 }, + timestamp: "1000000", + sequence_number: "1", + } as unknown as AptosTransaction, + ]; + + const result = txsToOps({ address }, id, txs); + + expect(result).toHaveLength(0); + }); + + it("should skip transactions that result in no Aptos change", () => { + const address = "0x11"; + const id = "test-id"; + const txs: AptosTransaction[] = [ + { + hash: "0x123", + sender: "0x12", + gas_used: "200", + gas_unit_price: "100", + success: true, + payload: { + type: "entry_function_payload", + function: "0x1::coin::transfer", + type_arguments: [], + arguments: ["0x11", 100], + } as EntryFunctionPayloadResponse, + events: [], + changes: [], + block: { hash: "0xabc", height: 1 }, + timestamp: "1000000", + sequence_number: "1", + } as unknown as AptosTransaction, + ]; + + const result = txsToOps({ address }, id, txs); + + expect(result).toHaveLength(0); + }); + + it("should handle failed transactions", () => { + const address = "0x11"; + const id = "test-id"; + const txs: AptosTransaction[] = [ + { + hash: "0x123", + sender: "0x11", + gas_used: "200", + gas_unit_price: "100", + success: false, + payload: { + type: "entry_function_payload", + function: "0x1::coin::transfer", + type_arguments: [], + arguments: ["0x12", 100], + } as EntryFunctionPayloadResponse, + events: [ + { + type: "0x1::coin::WithdrawEvent", + guid: { + account_address: "0x11", + creation_number: "1", + }, + data: { + amount: "100", + }, + }, + { + type: "0x1::coin::DepositEvent", + guid: { + account_address: "0x12", + creation_number: "2", + }, + data: { + amount: "100", + }, + }, + ], + changes: [ + { + type: "write_resource", + data: { + type: APTOS_COIN_CHANGE, + data: { + withdraw_events: { + guid: { + id: { + addr: "0x11", + creation_num: "1", + }, + }, + }, + deposit_events: { + guid: { + id: { + addr: "0x12", + creation_num: "2", + }, + }, + }, + }, + }, + }, + ], + block: { hash: "0xabc", height: 1 }, + timestamp: "1000000", + sequence_number: "1", + } as unknown as AptosTransaction, + ]; + + const result = txsToOps({ address }, id, txs); + + expect(result).toHaveLength(1); + expect(result[0].hasFailed).toBe(true); + }); + }); }); diff --git a/libs/ledger-live-common/src/families/aptos/logic.ts b/libs/ledger-live-common/src/families/aptos/logic.ts index 9d7c554c1355..33a7a4db00a5 100644 --- a/libs/ledger-live-common/src/families/aptos/logic.ts +++ b/libs/ledger-live-common/src/families/aptos/logic.ts @@ -16,33 +16,14 @@ import { DIRECTION, TRANSFER_TYPES, } from "./constants"; -import type { AptosTransaction, Transaction } from "./types"; +import type { AptosTransaction, TransactionOptions } from "./types"; export const DEFAULT_GAS = 200; export const DEFAULT_GAS_PRICE = 100; export const ESTIMATE_GAS_MUL = 1.0; // define buffer for gas estimation change here, if needed -const HEX_REGEXP = /^[-+]?[a-f0-9]+\.?[a-f0-9]*?$/i; const CLEAN_HEX_REGEXP = /^0x0*|^0+/; -const LENGTH_WITH_0x = 66; - -export function isValidAddress(address = ""): boolean { - let str = address; - - const validAddressWithOx = address.startsWith("0x") && address.length === LENGTH_WITH_0x; - - if (!validAddressWithOx) return false; - - str = str.substring(2); - - return isValidHex(str); -} - -function isValidHex(hex: string): boolean { - return HEX_REGEXP.test(hex); -} - export function isTestnet(currencyId: string): boolean { return getCryptoCurrencyById(currencyId).isTestnetFor ? true : false; } @@ -57,15 +38,8 @@ export const getMaxSendBalance = ( return amount.gt(totalGas) ? amount.minus(totalGas) : new BigNumber(0); }; -export function normalizeTransactionOptions( - options: Transaction["options"], -): Transaction["options"] { - const check = (v: any) => { - if (v === undefined || v === null || v === "") { - return undefined; - } - return v; - }; +export function normalizeTransactionOptions(options: TransactionOptions): TransactionOptions { + const check = (v: any) => ((v ?? "").toString().trim() ? v : undefined); return { maxGasAmount: check(options.maxGasAmount), gasUnitPrice: check(options.gasUnitPrice), @@ -74,7 +48,7 @@ export function normalizeTransactionOptions( }; } -const getBlankOperation = ( +export const getBlankOperation = ( tx: AptosTransaction, id: string, ): Operation> => ({ @@ -154,7 +128,7 @@ export function compareAddress(addressA: string, addressB: string) { } export function getFunctionAddress(payload: InputEntryFunctionData): string | undefined { - if ("function" in payload) { + if (payload.function) { const parts = payload.function.split("::"); return parts.length === 3 && parts[0].length ? parts[0] : undefined; } diff --git a/libs/ledger-live-common/src/families/aptos/prepareTransaction.test.ts b/libs/ledger-live-common/src/families/aptos/prepareTransaction.test.ts new file mode 100644 index 000000000000..3d17e1c115a5 --- /dev/null +++ b/libs/ledger-live-common/src/families/aptos/prepareTransaction.test.ts @@ -0,0 +1,135 @@ +import prepareTransaction from "./prepareTransaction"; +import { AptosAPI } from "./api"; +import { getEstimatedGas } from "./getFeesForTransaction"; +import { getMaxSendBalance } from "./logic"; +import BigNumber from "bignumber.js"; +import type { Account } from "@ledgerhq/types-live"; +import type { Transaction } from "./types"; + +jest.mock("./api"); +jest.mock("./getFeesForTransaction"); +jest.mock("./logic"); + +describe("Aptos prepareTransaction", () => { + describe("prepareTransaction", () => { + let account: Account; + let transaction: Transaction; + + beforeEach(() => { + account = { + id: "test-account-id", + name: "Test Account", + currency: { + id: "aptos", + name: "Aptos", + ticker: "APT", + units: [{ name: "Aptos", code: "APT", magnitude: 6 }], + }, + spendableBalance: new BigNumber(1000), + balance: new BigNumber(1000), + blockHeight: 0, + operations: [], + pendingOperations: [], + unit: { code: "APT", name: "Aptos", magnitude: 6 }, + lastSyncDate: new Date(), + subAccounts: [], + } as unknown as Account; + + transaction = { + amount: new BigNumber(0), + recipient: "", + useAllAmount: false, + fees: new BigNumber(0), + firstEmulation: true, + options: {}, + } as Transaction; + }); + + it("should return the transaction if recipient is not set", async () => { + const result = await prepareTransaction(account, transaction); + expect(result).toEqual(transaction); + }); + + it("should return the transaction with zero fees if amount is zero and useAllAmount is false", async () => { + transaction.recipient = "test-recipient"; + const result = await prepareTransaction(account, transaction); + expect(result.fees?.isZero()).toBe(true); + }); + + it("should set the amount to max sendable balance if useAllAmount is true", async () => { + transaction.recipient = "test-recipient"; + transaction.useAllAmount = true; + (getMaxSendBalance as jest.Mock).mockReturnValue(new BigNumber(900)); + (getEstimatedGas as jest.Mock).mockResolvedValue({ + fees: new BigNumber(2000), + estimate: { maxGasAmount: new BigNumber(200), gasUnitPrice: new BigNumber(10) }, + errors: {}, + }); + + const result = await prepareTransaction(account, transaction); + expect(result.amount.isEqualTo(new BigNumber(900))).toBe(true); + expect(result.fees?.isEqualTo(new BigNumber(2000))).toBe(true); + expect(new BigNumber(result.estimate.maxGasAmount).isEqualTo(new BigNumber(200))).toBe(true); + expect(result.errors).toEqual({}); + }); + + it("should call getEstimatedGas and set the transaction fees, estimate, and errors", async () => { + transaction.recipient = "test-recipient"; + transaction.amount = new BigNumber(100); + (getEstimatedGas as jest.Mock).mockResolvedValue({ + fees: new BigNumber(10), + estimate: { maxGasAmount: new BigNumber(200) }, + errors: {}, + }); + + const result = await prepareTransaction(account, transaction); + expect(getEstimatedGas).toHaveBeenCalledWith(account, transaction, expect.any(AptosAPI)); + expect(result.fees?.isEqualTo(new BigNumber(10))).toBe(true); + expect(new BigNumber(result.estimate.maxGasAmount).isEqualTo(new BigNumber(200))).toBe(true); + expect(result.errors).toEqual({}); + }); + + it("should set firstEmulation to false after the first call", async () => { + transaction.recipient = "test-recipient"; + transaction.amount = new BigNumber(100); + (getEstimatedGas as jest.Mock).mockResolvedValue({ + fees: new BigNumber(10), + estimate: { maxGasAmount: new BigNumber(200) }, + errors: {}, + }); + + const result = await prepareTransaction(account, transaction); + expect(result.firstEmulation).toBe(false); + }); + + it("should return the transaction with updated fees and estimate if recipient is set and amount is not zero", async () => { + transaction.recipient = "test-recipient"; + transaction.amount = new BigNumber(100); + (getEstimatedGas as jest.Mock).mockResolvedValue({ + fees: new BigNumber(2000), + estimate: { maxGasAmount: new BigNumber(200), gasUnitPrice: new BigNumber(10) }, + errors: {}, + }); + + const result = await prepareTransaction(account, transaction); + expect(result.fees?.isEqualTo(new BigNumber(2000))).toBe(true); + expect(new BigNumber(result.estimate.maxGasAmount).isEqualTo(new BigNumber(200))).toBe(true); + expect(result.errors).toEqual({}); + }); + + it("should set maxGasAmount in options", async () => { + transaction.recipient = "test-recipient"; + transaction.amount = new BigNumber(100); + transaction.firstEmulation = true; + (getEstimatedGas as jest.Mock).mockResolvedValue({ + fees: new BigNumber(2000), + estimate: { maxGasAmount: new BigNumber(200), gasUnitPrice: new BigNumber(10) }, + errors: {}, + }); + + const result = await prepareTransaction(account, transaction); + expect(new BigNumber(result.estimate.maxGasAmount).isEqualTo(new BigNumber(200))).toBe(true); + expect(result.firstEmulation).toBe(false); + }); + }); +});