import { setupMockCryptoAssetsStore } from "@ledgerhq/cryptoassets/cal-client/test-helpers";
import { encodeTokenAccountId } from "@ledgerhq/ledger-wallet-framework/account/accountId";
import { encodeOperationId } from "@ledgerhq/ledger-wallet-framework/operation";
import { getEnv } from "@ledgerhq/live-env";
import BigNumber from "bignumber.js";
import { apiClient } from "../network/api";
import { hgraphClient } from "../network/hgraph";
import * as networkUtils from "../network/utils";
import { getMockedEnrichedERC20Transfer } from "../test/fixtures/common.fixture";
import {
  getMockedCurrency,
  getMockedERC20TokenCurrency,
  getMockedHTSTokenCurrency,
} from "../test/fixtures/currency.fixture";
import { getMockedERC20TokenTransfer } from "../test/fixtures/hgraph.fixture";
import {
  getMockedMirrorAccount,
  getMockedMirrorContractCallResult,
  getMockedMirrorToken,
  getMockedMirrorTransaction,
} from "../test/fixtures/mirror.fixture";
import type { StakingAnalysis, SyntheticBlock } from "../types";
import { listOperationsV2 as listOperations } from "./listOperations.v2";
import * as utils from "./utils";

setupMockCryptoAssetsStore();

jest.mock("@ledgerhq/ledger-wallet-framework/account/accountId", () => ({
  ...jest.requireActual("@ledgerhq/ledger-wallet-framework/account/accountId"),
  encodeTokenAccountId: jest.fn(),
}));
jest.mock("@ledgerhq/ledger-wallet-framework/operation");
jest.mock("../network/api");
jest.mock("../network/hgraph");
jest.mock("../network/utils", () => ({
  ...jest.requireActual("../network/utils"),
  enrichERC20Transfers: jest.fn(),
}));
jest.mock("./utils", () => ({
  ...jest.requireActual("./utils"),
  base64ToUrlSafeBase64: jest.fn().mockImplementation(hash => `encoded-${hash}`),
  getMemoFromBase64: jest.fn().mockImplementation(memo => (memo ? `decoded-${memo}` : null)),
  getSyntheticBlock: jest.fn(),
  extractFeesPayer: jest.fn(),
  analyzeStakingOperation: jest.fn(),
}));

describe("listOperationsV2", () => {
  const mockCurrency = getMockedCurrency();
  const mockMirrorAccount = getMockedMirrorAccount({ account: "0.0.12345" });
  const mockSyntheticBlock: SyntheticBlock = {
    blockHeight: 1000000,
    blockHash: "0x100000",
    blockTime: new Date(),
  };
  const mockLimit = 10;
  const mockOrder = "desc";

  beforeEach(() => {
    jest.clearAllMocks();

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [],
      nextCursor: null,
    });
    (hgraphClient.getERC20Transfers as jest.Mock).mockResolvedValue([]);
    (hgraphClient.getLatestIndexedConsensusTimestamp as jest.Mock).mockResolvedValue(
      new BigNumber(0),
    );
    (encodeOperationId as jest.Mock).mockImplementation(
      (accountId, hash, type) => `${accountId}-${hash}-${type}`,
    );
    (encodeTokenAccountId as jest.Mock).mockImplementation(
      (accountId, token) => `${accountId}-${token.id}`,
    );
    (utils.getSyntheticBlock as jest.Mock).mockReturnValue(mockSyntheticBlock);
    (utils.extractFeesPayer as jest.Mock).mockImplementation(input =>
      typeof input === "string"
        ? input.split("-")[0]
        : (input.transaction_id?.split("-")[0] ?? "0.0.0"),
    );
    (utils.analyzeStakingOperation as jest.Mock).mockResolvedValue(null);
    (networkUtils.enrichERC20Transfers as jest.Mock).mockReturnValue([]);
  });

  it("should return empty arrays when no transactions are found", async () => {
    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [],
      nextCursor: null,
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(apiClient.getAccountTransactions).toHaveBeenCalledTimes(1);
    expect(apiClient.getAccountTransactions).toHaveBeenCalledWith({
      address: mockMirrorAccount.account,
      fetchAllPages: true,
      pagingToken: null,
      order: mockOrder,
      limit: mockLimit,
    });
    expect(hgraphClient.getERC20Transfers).toHaveBeenCalledTimes(1);
    expect(hgraphClient.getERC20Transfers).toHaveBeenCalledWith({
      address: mockMirrorAccount.account,
      fetchAllPages: true,
      order: mockOrder,
      limit: mockLimit,
      tokenEvmAddresses: [],
    });
    expect(hgraphClient.getLatestIndexedConsensusTimestamp).toHaveBeenCalledTimes(1);
    expect(result.coinOperations).toEqual([]);
    expect(result.tokenOperations).toEqual([]);
  });

  it("should parse HBAR transfer transactions correctly", async () => {
    const mockTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "hash1",
      charged_tx_fee: 500000,
      result: "SUCCESS",
      memo_base64: "test-memo",
      token_transfers: [],
      staking_reward_transfers: [],
      transfers: [
        { account: mockMirrorAccount.account, amount: -1000000 },
        { account: "0.0.67890", amount: 1000000 },
      ],
      name: "CRYPTOTRANSFER",
    });

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction],
      nextCursor: null,
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.tokenOperations).toEqual([]);
    expect(result.coinOperations).toHaveLength(1);
    expect(result.coinOperations).toMatchObject([
      {
        type: "OUT",
        value: expect.any(Object),
        hash: "hash1",
        fee: expect.any(Object),
        date: expect.any(Date),
        senders: [mockMirrorAccount.account],
        recipients: ["0.0.67890"],
        extra: {
          pagingToken: "1625097600.000000000",
          consensusTimestamp: "1625097600.000000000",
          memo: "decoded-test-memo",
        },
      },
    ]);
  });

  it("should parse HTS token transfer transactions correctly", async () => {
    const mockTokenHTS = getMockedHTSTokenCurrency();
    const mockTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "hash1",
      charged_tx_fee: 500000,
      result: "SUCCESS",
      token_transfers: [
        {
          token_id: mockTokenHTS.contractAddress,
          account: mockMirrorAccount.account,
          amount: -1000,
        },
        { token_id: mockTokenHTS.contractAddress, account: "0.0.67890", amount: 1000 },
      ],
      staking_reward_transfers: [],
      transfers: [],
      name: "CRYPTOTRANSFER",
    });

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction],
      nextCursor: null,
    });

    setupMockCryptoAssetsStore({
      findTokenByAddressInCurrency: jest.fn().mockResolvedValue(mockTokenHTS),
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.coinOperations).toMatchObject([
      {
        type: "FEES",
        fee: expect.any(Object),
      },
    ]);
    expect(result.tokenOperations).toMatchObject([
      {
        type: "OUT",
        value: expect.any(Object),
        hash: "hash1",
        contract: mockTokenHTS.contractAddress,
        standard: "hts",
        senders: [mockMirrorAccount.account],
        recipients: ["0.0.67890"],
        extra: {
          pagingToken: "1625097600.000000000",
          consensusTimestamp: "1625097600.000000000",
        },
      },
    ]);
  });

  it("should parse ERC20 token transfer transactions correctly", async () => {
    const mockTokenERC20 = getMockedERC20TokenCurrency();
    const sharedHash = "erc20-transfer-hash";
    const sharedTimestamp = "1625097600.000000000";
    const memo = "xyz";

    const mockMirrorTransaction = getMockedMirrorTransaction({
      consensus_timestamp: sharedTimestamp,
      transaction_hash: sharedHash,
      charged_tx_fee: 300000,
      result: "SUCCESS",
      name: "CONTRACTCALL",
      memo_base64: memo,
      staking_reward_transfers: [],
      token_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -300000 }],
    });
    const mockERC20Transfer = getMockedERC20TokenTransfer({
      transaction_hash: sharedHash,
      consensus_timestamp: Number(sharedTimestamp.split(".")[0]) * 10 ** 9,
      sender_account_id: 12345,
      receiver_account_id: 67890,
      sender_evm_address: mockMirrorAccount.evm_address,
      receiver_evm_address: "0xrecipient",
      payer_account_id: 12345,
      amount: 5000000,
    });
    const mockContractCallResult = getMockedMirrorContractCallResult({
      block_hash: "0xblockhash123",
      gas_consumed: 75000,
      gas_limit: 100000,
      gas_used: 75000,
    });
    const mockEnrichedERC20Transfer = getMockedEnrichedERC20Transfer({
      mirrorTransaction: mockMirrorTransaction,
      contractCallResult: mockContractCallResult,
      transfers: [mockERC20Transfer],
    });

    jest.spyOn(networkUtils, "enrichERC20Transfers").mockResolvedValue([mockEnrichedERC20Transfer]);
    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [],
      nextCursor: null,
    });
    (hgraphClient.getERC20Transfers as jest.Mock).mockResolvedValue([mockERC20Transfer]);
    (hgraphClient.getLatestIndexedConsensusTimestamp as jest.Mock).mockResolvedValue(
      new BigNumber(sharedTimestamp),
    );

    setupMockCryptoAssetsStore({
      findTokenByAddressInCurrency: jest.fn().mockResolvedValue(mockTokenERC20),
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [{ token: mockTokenERC20, balance: new BigNumber(10000000) }],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.tokenOperations).toEqual([
      expect.objectContaining({
        type: "OUT",
        standard: "erc20",
        contract: mockTokenERC20.contractAddress,
        hash: mockMirrorTransaction.transaction_hash,
        value: new BigNumber(5000000),
        fee: new BigNumber(300000),
        senders: ["0.0.12345"],
        recipients: ["0.0.67890"],
        blockHash: null,
        extra: expect.objectContaining({
          pagingToken: mockMirrorTransaction.consensus_timestamp,
          consensusTimestamp: mockMirrorTransaction.consensus_timestamp,
          memo: `decoded-${mockMirrorTransaction.memo_base64}`,
          gasConsumed: mockContractCallResult.gas_consumed,
          gasLimit: mockContractCallResult.gas_limit,
          gasUsed: mockContractCallResult.gas_used,
        }),
      }),
    ]);
    expect(result.coinOperations).toEqual([
      expect.objectContaining({
        type: "FEES",
        value: new BigNumber(300000),
        hash: sharedHash,
      }),
    ]);
  });

  it("should use EVM address for sender/recipient when account_id is null in ERC20 transfer", async () => {
    const mockTokenERC20 = getMockedERC20TokenCurrency();
    const sharedHash = "erc20-transfer-hash";
    const sharedTimestamp = "1625097600.000000000";

    const mockMirrorTransaction = getMockedMirrorTransaction({
      consensus_timestamp: sharedTimestamp,
      transaction_hash: sharedHash,
      charged_tx_fee: 300000,
      result: "SUCCESS",
      name: "CONTRACTCALL",
      staking_reward_transfers: [],
      token_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -300000 }],
    });
    const mockERC20Transfer = getMockedERC20TokenTransfer({
      transaction_hash: sharedHash,
      consensus_timestamp: Number(sharedTimestamp.split(".")[0]) * 10 ** 9,
      sender_account_id: null,
      receiver_account_id: null,
      sender_evm_address: mockMirrorAccount.evm_address,
      receiver_evm_address: "0xrecipient123",
      payer_account_id: 12345,
      amount: 5000000,
    });
    const mockContractCallResult = getMockedMirrorContractCallResult();
    const mockEnrichedERC20Transfer = getMockedEnrichedERC20Transfer({
      mirrorTransaction: mockMirrorTransaction,
      contractCallResult: mockContractCallResult,
      transfers: [mockERC20Transfer],
    });

    jest.spyOn(networkUtils, "enrichERC20Transfers").mockResolvedValue([mockEnrichedERC20Transfer]);
    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [],
      nextCursor: null,
    });
    (hgraphClient.getERC20Transfers as jest.Mock).mockResolvedValue([mockERC20Transfer]);
    (hgraphClient.getLatestIndexedConsensusTimestamp as jest.Mock).mockResolvedValue(
      new BigNumber(sharedTimestamp),
    );

    setupMockCryptoAssetsStore({
      findTokenByAddressInCurrency: jest.fn().mockResolvedValue(mockTokenERC20),
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [{ token: mockTokenERC20, balance: new BigNumber(10000000) }],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.tokenOperations).toEqual([
      expect.objectContaining({
        senders: [mockMirrorAccount.evm_address],
        recipients: [mockERC20Transfer.receiver_evm_address],
      }),
    ]);
  });

  it("should skip ERC20 operations when sender evm address is null", async () => {
    const mockTokenERC20 = getMockedERC20TokenCurrency();
    const sharedHash = "erc20-null-sender-hash";
    const sharedTimestamp = "1625097600.000000000";
    const mockMirrorTransaction = getMockedMirrorTransaction({
      consensus_timestamp: sharedTimestamp,
      transaction_hash: sharedHash,
      name: "CONTRACTCALL",
      transfers: [{ account: mockMirrorAccount.account, amount: -300000 }],
    });
    const mockERC20Transfer = getMockedERC20TokenTransfer({
      transaction_hash: sharedHash,
      consensus_timestamp: Number(sharedTimestamp.split(".")[0]) * 10 ** 9,
      sender_account_id: null,
      receiver_account_id: 67890,
      sender_evm_address: null,
      receiver_evm_address: "0xrecipient",
      payer_account_id: 12345,
      amount: 5000000,
    });
    const mockContractCallResult = getMockedMirrorContractCallResult();
    const mockEnrichedERC20Transfer = getMockedEnrichedERC20Transfer({
      mirrorTransaction: mockMirrorTransaction,
      contractCallResult: mockContractCallResult,
      transfers: [mockERC20Transfer],
    });

    jest.spyOn(networkUtils, "enrichERC20Transfers").mockResolvedValue([mockEnrichedERC20Transfer]);
    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [],
      nextCursor: null,
    });
    (hgraphClient.getERC20Transfers as jest.Mock).mockResolvedValue([mockERC20Transfer]);
    (hgraphClient.getLatestIndexedConsensusTimestamp as jest.Mock).mockResolvedValue(
      new BigNumber(sharedTimestamp),
    );

    setupMockCryptoAssetsStore({
      findTokenByAddressInCurrency: jest.fn().mockResolvedValue(mockTokenERC20),
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [{ token: mockTokenERC20, balance: new BigNumber(10000000) }],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.tokenOperations).toEqual([]);
    expect(result.coinOperations).toEqual([]);
  });

  it("should parse token associate transactions correctly", async () => {
    const mockTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "hash1",
      charged_tx_fee: 500000,
      result: "SUCCESS",
      token_transfers: [],
      staking_reward_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -500000 }],
      name: "TOKENASSOCIATE",
    });

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction],
      nextCursor: null,
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.tokenOperations).toEqual([]);
    expect(result.coinOperations).toMatchObject([
      {
        type: "ASSOCIATE_TOKEN",
        value: expect.any(Object),
        hash: "hash1",
        fee: expect.any(Object),
        senders: [mockMirrorAccount.account],
        recipients: ["0.0.3"],
        extra: {
          pagingToken: "1625097600.000000000",
          consensusTimestamp: "1625097600.000000000",
        },
      },
    ]);
  });

  it("should include associatedTokenId in extra when ASSOCIATE_TOKEN creates a token", async () => {
    const mockTokenHTS = getMockedHTSTokenCurrency();
    const mockMirrorTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "hash1",
      charged_tx_fee: 500000,
      result: "SUCCESS",
      token_transfers: [],
      staking_reward_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -500000 }],
      name: "TOKENASSOCIATE",
    });

    const mockMirrorToken = getMockedMirrorToken({
      token_id: mockTokenHTS.contractAddress,
      created_timestamp: mockMirrorTransaction.consensus_timestamp,
    });

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockMirrorTransaction],
      nextCursor: null,
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [mockMirrorToken],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.tokenOperations).toEqual([]);
    expect(result.coinOperations).toEqual([
      expect.objectContaining({
        type: "ASSOCIATE_TOKEN",
        hash: "hash1",
        extra: expect.objectContaining({
          associatedTokenId: mockTokenHTS.contractAddress,
        }),
      }),
    ]);
  });

  it("should skip token operations when token is not found in cryptoassets", async () => {
    const tokenId = "0.0.7890";
    const mockTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "hash1",
      charged_tx_fee: 500000,
      result: "SUCCESS",
      token_transfers: [
        { token_id: tokenId, account: mockMirrorAccount.account, amount: -1000 },
        { token_id: tokenId, account: "0.0.67890", amount: 1000 },
      ],
      staking_reward_transfers: [],
      transfers: [],
      name: "CRYPTOTRANSFER",
    });

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction],
      nextCursor: null,
    });

    setupMockCryptoAssetsStore({
      findTokenByAddressInCurrency: jest.fn().mockResolvedValue(null),
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.coinOperations).toEqual([]);
    expect(result.tokenOperations).toEqual([]);
  });

  it("should use pagination parameters correctly", async () => {
    const customOrder = "asc";
    const customLimit = 20;
    const lastPagingToken = "1625097500.000000000";

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [],
      nextCursor: null,
    });

    await listOperations({
      limit: customLimit,
      order: customOrder,
      cursor: lastPagingToken,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(apiClient.getAccountTransactions).toHaveBeenCalledTimes(1);
    expect(apiClient.getAccountTransactions).toHaveBeenCalledWith({
      address: mockMirrorAccount.account,
      fetchAllPages: true,
      pagingToken: lastPagingToken,
      order: customOrder,
      limit: customLimit,
    });
  });

  it("should handle failed transactions", async () => {
    const mockTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "hash1",
      charged_tx_fee: 500000,
      result: "INVALID_SIGNATURE",
      memo_base64: "",
      token_transfers: [],
      staking_reward_transfers: [],
      transfers: [
        { account: mockMirrorAccount.account, amount: -1000000 },
        { account: "0.0.67890", amount: 1000000 },
      ],
      name: "CRYPTOTRANSFER",
    });

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction],
      nextCursor: null,
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      currency: mockCurrency,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.coinOperations).toMatchObject([{ hasFailed: true }]);
  });

  it("should include inferred fees payer in operation extra", async () => {
    (utils.extractFeesPayer as jest.Mock).mockReturnValue("0.0.23");

    const mockTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "hash1",
      transaction_id: "0.0.10067173-1761755118-730000493",
      charged_tx_fee: 40743,
      result: "INSUFFICIENT_PAYER_BALANCE",
      token_transfers: [],
      staking_reward_transfers: [],
      transfers: [
        { account: "0.0.23", amount: -40743 },
        { account: "0.0.801", amount: 40743 },
      ],
      name: "CRYPTOTRANSFER",
    });

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction],
      nextCursor: null,
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      currency: mockCurrency,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.coinOperations).toMatchObject([
      {
        extra: {
          transactionId: "0.0.10067173-1761755118-730000493",
          feesPayer: "0.0.23",
        },
      },
    ]);
  });

  it("should create REWARD operation when staking rewards are present", async () => {
    const mockTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "hash1",
      charged_tx_fee: 500000,
      result: "SUCCESS",
      memo_base64: "",
      token_transfers: [],
      staking_reward_transfers: [{ account: mockMirrorAccount.account, amount: 1000000 }],
      transfers: [{ account: mockMirrorAccount.account, amount: -500000 }],
      name: "CRYPTOTRANSFER",
    });

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction],
      nextCursor: null,
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      currency: mockCurrency,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    const rewardTimestamp = result.coinOperations[0].date.getTime();
    const mainTimestamp = result.coinOperations[1].date.getTime();

    expect(result.tokenOperations).toEqual([]);
    expect(rewardTimestamp).toBe(mainTimestamp + 1);
    expect(result.coinOperations).toMatchObject([
      {
        type: "REWARD",
        hash: utils.createStakingRewardOperationHash(mockTransaction.transaction_hash ?? ""),
        value: new BigNumber(1000000),
        fee: new BigNumber(0),
        senders: [getEnv("HEDERA_STAKING_REWARD_ACCOUNT_ID")],
        recipients: [mockMirrorAccount.account],
      },
      {
        type: "OUT",
        hash: mockTransaction.transaction_hash,
      },
    ]);
  });

  it("should create REWARD operation for ERC20 transfers with staking rewards", async () => {
    const mockTokenERC20 = getMockedERC20TokenCurrency();
    const mockRewardAmount = 10000000;

    const mockMirrorTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "erc20-transfer-hash",
      charged_tx_fee: 300000,
      result: "SUCCESS",
      name: "CONTRACTCALL",
      staking_reward_transfers: [{ account: mockMirrorAccount.account, amount: mockRewardAmount }],
      token_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -300000 }],
    });

    const mockERC20Transfer = getMockedERC20TokenTransfer({
      transaction_hash: mockMirrorTransaction.transaction_hash,
      consensus_timestamp:
        Number(mockMirrorTransaction.consensus_timestamp.split(".")[0]) * 10 ** 9,
      sender_account_id: 12345,
      receiver_account_id: 67890,
      sender_evm_address: mockMirrorAccount.evm_address,
      receiver_evm_address: "0xrecipient",
      payer_account_id: 12345,
      amount: 5000000,
    });

    const mockContractCallResult = getMockedMirrorContractCallResult();
    const mockEnrichedERC20Transfer = getMockedEnrichedERC20Transfer({
      mirrorTransaction: mockMirrorTransaction,
      contractCallResult: mockContractCallResult,
      transfers: [mockERC20Transfer],
    });

    jest.spyOn(networkUtils, "enrichERC20Transfers").mockResolvedValue([mockEnrichedERC20Transfer]);
    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [],
      nextCursor: null,
    });
    (hgraphClient.getERC20Transfers as jest.Mock).mockResolvedValue([mockERC20Transfer]);
    (hgraphClient.getLatestIndexedConsensusTimestamp as jest.Mock).mockResolvedValue(
      new BigNumber(mockMirrorTransaction.consensus_timestamp),
    );

    setupMockCryptoAssetsStore({
      findTokenByAddressInCurrency: jest.fn().mockResolvedValue(mockTokenERC20),
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [{ token: mockTokenERC20, balance: new BigNumber(10000000) }],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.tokenOperations).toEqual([
      expect.objectContaining({
        type: "OUT",
        standard: "erc20",
        contract: mockTokenERC20.contractAddress,
        hash: mockMirrorTransaction.transaction_hash,
        value: new BigNumber(mockERC20Transfer.amount),
      }),
    ]);
    expect(result.coinOperations).toEqual([
      expect.objectContaining({
        type: "REWARD",
        hash: utils.createStakingRewardOperationHash(mockMirrorTransaction.transaction_hash ?? ""),
        value: new BigNumber(mockRewardAmount),
      }),
      expect.objectContaining({
        type: "FEES",
        hash: mockMirrorTransaction.transaction_hash,
        value: new BigNumber(mockMirrorTransaction.charged_tx_fee),
      }),
    ]);
  });

  it("should create STAKE operation when UPDATE_ACCOUNT transaction stakes to a node", async () => {
    const mockTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "hash1",
      charged_tx_fee: 500000,
      result: "SUCCESS",
      memo_base64: "",
      token_transfers: [],
      staking_reward_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -500000 }],
      name: "CRYPTOUPDATEACCOUNT",
    });

    const mockStakingAnalysis: StakingAnalysis = {
      operationType: "STAKE",
      previousStakingNodeId: null,
      targetStakingNodeId: 3,
      stakedAmount: BigInt(1000000000),
    };

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction],
      nextCursor: null,
    });
    (utils.analyzeStakingOperation as jest.Mock).mockResolvedValue(mockStakingAnalysis);

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.tokenOperations).toEqual([]);
    expect(result.coinOperations).toEqual([
      expect.objectContaining({
        type: "STAKE",
        hash: "hash1",
        fee: new BigNumber(500000),
        extra: expect.objectContaining({
          previousStakingNodeId: null,
          targetStakingNodeId: 3,
          stakedAmount: new BigNumber(1000000000),
        }),
      }),
    ]);
  });

  it("should create UNSTAKE operation when UPDATE_ACCOUNT transaction removes staking", async () => {
    const mockTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "hash1",
      charged_tx_fee: 500000,
      result: "SUCCESS",
      memo_base64: "",
      token_transfers: [],
      staking_reward_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -500000 }],
      name: "CRYPTOUPDATEACCOUNT",
    });

    const mockStakingAnalysis: StakingAnalysis = {
      operationType: "UNSTAKE",
      previousStakingNodeId: 3,
      targetStakingNodeId: null,
      stakedAmount: BigInt(0),
    };

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction],
      nextCursor: null,
    });
    (utils.analyzeStakingOperation as jest.Mock).mockResolvedValue(mockStakingAnalysis);

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.tokenOperations).toEqual([]);
    expect(result.coinOperations).toEqual([
      expect.objectContaining({
        type: "UNSTAKE",
        hash: "hash1",
        fee: new BigNumber(500000),
        extra: expect.objectContaining({
          previousStakingNodeId: 3,
          targetStakingNodeId: null,
          stakedAmount: new BigNumber(0),
        }),
      }),
    ]);
  });

  it("should skip FEES operations for HTS IN transfers", async () => {
    const mockTokenHTS = getMockedHTSTokenCurrency();
    const mockTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "hash1",
      charged_tx_fee: 500000,
      result: "SUCCESS",
      token_transfers: [
        { token_id: mockTokenHTS.contractAddress, account: "0.0.67890", amount: -1000 },
        {
          token_id: mockTokenHTS.contractAddress,
          account: mockMirrorAccount.account,
          amount: 1000,
        },
      ],
      staking_reward_transfers: [],
      transfers: [],
      name: "CRYPTOTRANSFER",
    });

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction],
      nextCursor: null,
    });

    setupMockCryptoAssetsStore({
      findTokenByAddressInCurrency: jest.fn().mockResolvedValue(mockTokenHTS),
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.coinOperations).toEqual([]);
    expect(result.tokenOperations).toEqual([expect.objectContaining({ type: "IN" })]);
  });

  it("should skip FEES operations for ERC20 IN transfers", async () => {
    const mockTokenERC20 = getMockedERC20TokenCurrency();
    const sharedHash = "erc20-in-transfer-hash";
    const sharedTimestamp = "1625097600.000000000";
    const mockMirrorTransaction = getMockedMirrorTransaction({
      consensus_timestamp: sharedTimestamp,
      transaction_hash: sharedHash,
      charged_tx_fee: 300000,
      result: "SUCCESS",
      name: "CONTRACTCALL",
      staking_reward_transfers: [],
      token_transfers: [],
      transfers: [],
    });
    const mockERC20Transfer = getMockedERC20TokenTransfer({
      transaction_hash: sharedHash,
      consensus_timestamp: Number(sharedTimestamp.split(".")[0]) * 10 ** 9,
      sender_account_id: 67890,
      receiver_account_id: 12345,
      sender_evm_address: "0xsender",
      receiver_evm_address: mockMirrorAccount.evm_address,
      payer_account_id: 67890,
      amount: 5000000,
    });
    const mockContractCallResult = getMockedMirrorContractCallResult();
    const mockEnrichedERC20Transfer = getMockedEnrichedERC20Transfer({
      mirrorTransaction: mockMirrorTransaction,
      contractCallResult: mockContractCallResult,
      transfers: [mockERC20Transfer],
    });

    jest.spyOn(networkUtils, "enrichERC20Transfers").mockResolvedValue([mockEnrichedERC20Transfer]);
    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [],
      nextCursor: null,
    });
    (hgraphClient.getERC20Transfers as jest.Mock).mockResolvedValue([mockERC20Transfer]);
    (hgraphClient.getLatestIndexedConsensusTimestamp as jest.Mock).mockResolvedValue(
      new BigNumber(sharedTimestamp),
    );

    setupMockCryptoAssetsStore({
      findTokenByAddressInCurrency: jest.fn().mockResolvedValue(mockTokenERC20),
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [{ token: mockTokenERC20, balance: new BigNumber(10000000) }],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.coinOperations).toEqual([]);
    expect(result.tokenOperations).toEqual([expect.objectContaining({ type: "IN" })]);
  });

  it("should produce two token operations for a swap with two different-token transfers", async () => {
    const sharedHash = "erc20-in-transfer-hash";
    const mockTokenA = getMockedERC20TokenCurrency({
      id: "hedera/erc20/0xTokenA",
      contractAddress: "0xTokenA",
    });
    const mockTokenB = getMockedERC20TokenCurrency({
      id: "hedera/erc20/0xTokenB",
      contractAddress: "0xTokenB",
    });

    const mockErc20TransferOut = getMockedERC20TokenTransfer({
      token_evm_address: mockTokenA.contractAddress,
      sender_evm_address: mockMirrorAccount.evm_address,
      sender_account_id: 12345,
      receiver_account_id: 99999,
      transfer_type: "transfer",
      amount: 1000,
      transaction_hash: sharedHash,
    });
    const mockErc20TransferIn = getMockedERC20TokenTransfer({
      token_evm_address: mockTokenB.contractAddress,
      receiver_evm_address: mockMirrorAccount.evm_address,
      sender_account_id: 99999,
      receiver_account_id: 12345,
      transfer_type: "transfer",
      amount: 2000,
      transaction_hash: sharedHash,
    });

    const mockEnrichedERC20Transfer = getMockedEnrichedERC20Transfer({
      transfers: [mockErc20TransferOut, mockErc20TransferIn],
    });

    jest.spyOn(networkUtils, "enrichERC20Transfers").mockResolvedValue([mockEnrichedERC20Transfer]);
    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [],
      nextCursor: null,
    });
    (hgraphClient.getERC20Transfers as jest.Mock).mockResolvedValue([]);
    (hgraphClient.getLatestIndexedConsensusTimestamp as jest.Mock).mockResolvedValue(
      new BigNumber("1625097600.000000000"),
    );

    setupMockCryptoAssetsStore({
      findTokenByAddressInCurrency: jest
        .fn()
        .mockResolvedValueOnce(mockTokenA) // for transfer out
        .mockResolvedValueOnce(mockTokenB), // for transfer in
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [
        { token: mockTokenA, balance: new BigNumber(10000) },
        { token: mockTokenB, balance: new BigNumber(20000) },
      ],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.coinOperations).toEqual([expect.objectContaining({ type: "FEES" })]);
    expect(result.tokenOperations).toEqual([
      expect.objectContaining({ type: "OUT" }),
      expect.objectContaining({ type: "IN" }),
    ]);
  });

  it("should skip FEES operations when skipFeesForTokenOperations is true", async () => {
    const mockTokenHTS = getMockedHTSTokenCurrency();
    const mockTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "hash1",
      charged_tx_fee: 500000,
      result: "SUCCESS",
      token_transfers: [
        {
          token_id: mockTokenHTS.contractAddress,
          account: mockMirrorAccount.account,
          amount: -1000,
        },
        { token_id: mockTokenHTS.contractAddress, account: "0.0.67890", amount: 1000 },
      ],
      staking_reward_transfers: [],
      transfers: [],
      name: "CRYPTOTRANSFER",
    });

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction],
      nextCursor: null,
    });

    setupMockCryptoAssetsStore({
      findTokenByAddressInCurrency: jest.fn().mockResolvedValue(mockTokenHTS),
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: true,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.coinOperations).toHaveLength(0);
    expect(result.tokenOperations).toHaveLength(1);
    expect(result.tokenOperations[0].type).toBe("OUT");
  });

  it("should use encoded hash when useEncodedHash is true", async () => {
    const mockTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "hash1",
      charged_tx_fee: 500000,
      result: "SUCCESS",
      memo_base64: "",
      token_transfers: [],
      staking_reward_transfers: [],
      transfers: [
        { account: mockMirrorAccount.account, amount: -1000000 },
        { account: "0.0.67890", amount: 1000000 },
      ],
      name: "CRYPTOTRANSFER",
    });

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction],
      nextCursor: null,
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: true,
      useSyntheticBlocks: false,
    });

    expect(result.coinOperations).toHaveLength(1);
    expect(result.coinOperations[0].hash).toBe("encoded-hash1");
  });

  it("should use synthetic blocks when useSyntheticBlocks is true", async () => {
    const mockTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000000",
      transaction_hash: "hash1",
      charged_tx_fee: 500000,
      result: "SUCCESS",
      memo_base64: "",
      token_transfers: [],
      staking_reward_transfers: [],
      transfers: [
        { account: mockMirrorAccount.account, amount: -1000000 },
        { account: "0.0.67890", amount: 1000000 },
      ],
      name: "CRYPTOTRANSFER",
    });

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction],
      nextCursor: null,
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: true,
    });

    expect(utils.getSyntheticBlock).toHaveBeenCalledTimes(1);
    expect(result.coinOperations).toEqual([
      expect.objectContaining({
        blockHeight: mockSyntheticBlock.blockHeight,
        blockHash: mockSyntheticBlock.blockHash,
      }),
    ]);
  });

  it("should use synthetic block hash for ERC20 transfers when useSyntheticBlocks is true", async () => {
    const mockTokenERC20 = getMockedERC20TokenCurrency();
    const sharedHash = "erc20-transfer-hash-synthetic";
    const sharedTimestamp = "1625097600.000000000";
    const mockMirrorTransaction = getMockedMirrorTransaction({
      consensus_timestamp: sharedTimestamp,
      transaction_hash: sharedHash,
      charged_tx_fee: 300000,
      result: "SUCCESS",
      name: "CONTRACTCALL",
      staking_reward_transfers: [],
      token_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -300000 }],
    });
    const mockERC20Transfer = getMockedERC20TokenTransfer({
      transaction_hash: sharedHash,
      consensus_timestamp: Number(sharedTimestamp.split(".")[0]) * 10 ** 9,
      sender_account_id: 12345,
      receiver_account_id: 67890,
      sender_evm_address: mockMirrorAccount.evm_address,
      receiver_evm_address: "0xrecipient",
      payer_account_id: 12345,
      amount: 5000000,
    });
    const mockContractCallResult = getMockedMirrorContractCallResult({
      block_hash: "0xevm-block-hash-should-not-be-used",
    });
    const mockEnrichedERC20Transfer = getMockedEnrichedERC20Transfer({
      mirrorTransaction: mockMirrorTransaction,
      contractCallResult: mockContractCallResult,
      transfers: [mockERC20Transfer],
    });

    jest.spyOn(networkUtils, "enrichERC20Transfers").mockResolvedValue([mockEnrichedERC20Transfer]);
    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [],
      nextCursor: null,
    });
    (hgraphClient.getERC20Transfers as jest.Mock).mockResolvedValue([mockERC20Transfer]);
    (hgraphClient.getLatestIndexedConsensusTimestamp as jest.Mock).mockResolvedValue(
      new BigNumber(sharedTimestamp),
    );

    setupMockCryptoAssetsStore({
      findTokenByAddressInCurrency: jest.fn().mockResolvedValue(mockTokenERC20),
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [{ token: mockTokenERC20, balance: new BigNumber(10000000) }],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: true,
    });

    expect(result.tokenOperations).toEqual([
      expect.objectContaining({
        type: "OUT",
        standard: "erc20",
        blockHeight: mockSyntheticBlock.blockHeight,
        blockHash: mockSyntheticBlock.blockHash,
      }),
    ]);
  });

  it("should deduplicate CONTRACT_CALL operations when ERC20 transfer exists for same hash", async () => {
    const sharedHash = "contract-call-hash";
    const sharedTimestamp = "1625097600.000000000";
    const mockTokenERC20 = getMockedERC20TokenCurrency();

    const mockMirrorTransaction = getMockedMirrorTransaction({
      consensus_timestamp: sharedTimestamp,
      transaction_hash: sharedHash,
      charged_tx_fee: 200000,
      result: "SUCCESS",
      name: "CONTRACTCALL",
      staking_reward_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -200000 }],
      token_transfers: [],
    });
    const mockERC20Transfer = getMockedERC20TokenTransfer({
      transaction_hash: sharedHash,
      consensus_timestamp: Number(sharedTimestamp) * 10 ** 9,
      sender_account_id: 1234,
      sender_evm_address: mockMirrorAccount.evm_address,
      payer_account_id: 1234,
      amount: 1000000,
    });
    const mockContractCallResult = getMockedMirrorContractCallResult();
    const mockEnrichedERC20Transfer = getMockedEnrichedERC20Transfer({
      mirrorTransaction: mockMirrorTransaction,
      contractCallResult: mockContractCallResult,
      transfers: [mockERC20Transfer],
    });

    jest.spyOn(networkUtils, "enrichERC20Transfers").mockResolvedValue([mockEnrichedERC20Transfer]);
    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockMirrorTransaction],
      nextCursor: null,
    });
    (hgraphClient.getERC20Transfers as jest.Mock).mockResolvedValue([mockERC20Transfer]);
    (apiClient.getContractCallResult as jest.Mock).mockResolvedValue(mockContractCallResult);
    (apiClient.findTransactionByContractCall as jest.Mock).mockResolvedValue(mockMirrorTransaction);
    (hgraphClient.getLatestIndexedConsensusTimestamp as jest.Mock).mockResolvedValue(
      new BigNumber(sharedTimestamp),
    );

    setupMockCryptoAssetsStore({
      findTokenByAddressInCurrency: jest.fn().mockResolvedValue(mockTokenERC20),
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [{ token: mockTokenERC20, balance: new BigNumber(5000000) }],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.coinOperations).toEqual([expect.objectContaining({ type: "FEES" })]);
    expect(result.tokenOperations).toEqual([
      expect.objectContaining({ type: "OUT", standard: "erc20" }),
    ]);
  });

  it("should sort with nanosecond precision", async () => {
    const mockTransaction1 = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000003",
      transaction_hash: "hash3",
      charged_tx_fee: 100000,
      result: "SUCCESS",
      name: "CRYPTOTRANSFER",
      token_transfers: [],
      staking_reward_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -300000 }],
    });
    const mockTransaction2 = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000001",
      transaction_hash: "hash1",
      charged_tx_fee: 100000,
      result: "SUCCESS",
      name: "CRYPTOTRANSFER",
      token_transfers: [],
      staking_reward_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -100000 }],
    });
    const mockTransaction3 = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000002",
      transaction_hash: "hash2",
      charged_tx_fee: 100000,
      result: "SUCCESS",
      name: "CRYPTOTRANSFER",
      token_transfers: [],
      staking_reward_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -200000 }],
    });

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction1, mockTransaction2, mockTransaction3],
      nextCursor: null,
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.coinOperations.map(op => op.hash)).toEqual(["hash3", "hash2", "hash1"]);
  });

  it("should merge and sort operations from both mirror and hgraph sources", async () => {
    const mockTokenERC20 = getMockedERC20TokenCurrency();
    const mockMirrorTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000003",
      transaction_hash: "mirror-hash",
      charged_tx_fee: 500000,
      result: "SUCCESS",
      name: "CRYPTOTRANSFER",
      staking_reward_transfers: [],
      token_transfers: [],
      transfers: [
        { account: mockMirrorAccount.account, amount: -1000000 },
        { account: "0.0.67890", amount: 1000000 },
      ],
    });
    const mockERC20MirrorTransaction = getMockedMirrorTransaction({
      consensus_timestamp: "1625097600.000000001",
      transaction_hash: "erc20-hash",
      charged_tx_fee: 300000,
      result: "SUCCESS",
      name: "CONTRACTCALL",
      staking_reward_transfers: [],
      token_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -300000 }],
    });
    const mockERC20Transfer = getMockedERC20TokenTransfer({
      transaction_hash: mockERC20MirrorTransaction.transaction_hash,
      consensus_timestamp:
        Number(mockERC20MirrorTransaction.consensus_timestamp.split(".")[0]) * 10 ** 9 + 1,
      sender_account_id: 12345,
      receiver_account_id: 67890,
      sender_evm_address: mockMirrorAccount.evm_address,
      receiver_evm_address: "0xrecipient",
      payer_account_id: 12345,
      amount: 5000000,
    });

    const mockContractCallResult = getMockedMirrorContractCallResult();
    const mockEnrichedERC20Transfer = getMockedEnrichedERC20Transfer({
      mirrorTransaction: mockERC20MirrorTransaction,
      contractCallResult: mockContractCallResult,
      transfers: [mockERC20Transfer],
    });

    jest.spyOn(networkUtils, "enrichERC20Transfers").mockResolvedValue([mockEnrichedERC20Transfer]);
    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockMirrorTransaction],
      nextCursor: null,
    });
    (hgraphClient.getERC20Transfers as jest.Mock).mockResolvedValue([mockERC20Transfer]);
    (hgraphClient.getLatestIndexedConsensusTimestamp as jest.Mock).mockResolvedValue(
      new BigNumber("1625097600.000000003"),
    );

    setupMockCryptoAssetsStore({
      findTokenByAddressInCurrency: jest.fn().mockResolvedValue(mockTokenERC20),
    });

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [{ token: mockTokenERC20, balance: new BigNumber(10000000) }],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.tokenOperations).toHaveLength(1);
    expect(result.coinOperations).toEqual([
      expect.objectContaining({ hash: mockMirrorTransaction.transaction_hash, type: "OUT" }),
      expect.objectContaining({ hash: mockERC20MirrorTransaction.transaction_hash, type: "FEES" }),
    ]);
  });

  it("should use rawTx.node as recipient when recipients array is empty", async () => {
    const nodeAccountId = "0.0.5";
    const mockTransaction = getMockedMirrorTransaction({
      node: nodeAccountId,
      token_transfers: [],
      staking_reward_transfers: [],
      transfers: [{ account: mockMirrorAccount.account, amount: -500000 }],
    });

    (apiClient.getAccountTransactions as jest.Mock).mockResolvedValue({
      transactions: [mockTransaction],
      nextCursor: null,
    });
    (utils.analyzeStakingOperation as jest.Mock).mockResolvedValue(null);

    const result = await listOperations({
      limit: mockLimit,
      order: mockOrder,
      currency: mockCurrency,
      address: mockMirrorAccount.account,
      evmAddress: mockMirrorAccount.evm_address,
      mirrorTokens: [],
      erc20Tokens: [],
      fetchAllPages: true,
      skipFeesForTokenOperations: false,
      useEncodedHash: false,
      useSyntheticBlocks: false,
    });

    expect(result.coinOperations).toHaveLength(1);
    expect(result.coinOperations[0].recipients).toEqual([nodeAccountId]);
  });
});
