@ledgerhq/coin-hedera
Version:
Ledger Hedera Coin integration
467 lines (404 loc) • 13.5 kB
text/typescript
import network from "@ledgerhq/live-network";
import BigNumber from "bignumber.js";
import { getMockResponse } from "../test/fixtures/network.fixture";
import { hgraphClient } from "./hgraph";
jest.mock("@ledgerhq/live-network");
const mockedNetwork = jest.mocked(network);
const getRequestData = (callIndex = 0) =>
(mockedNetwork.mock.calls[callIndex][0] as { data: { query: string; variables: any } }).data;
describe("getLatestIndexedConsensusTimestamp", () => {
beforeEach(() => {
jest.resetAllMocks();
});
it("should fetch and return the latest indexed consensus timestamp", async () => {
const mockTimestamp = "1234567890123456789";
mockedNetwork.mockResolvedValueOnce(
getMockResponse({
data: {
ethereum_transaction: [{ consensus_timestamp: mockTimestamp }],
},
}),
);
const result = await hgraphClient.getLatestIndexedConsensusTimestamp();
expect(mockedNetwork).toHaveBeenCalledTimes(1);
expect(result).toEqual(new BigNumber(mockTimestamp));
});
it("should throw error when API returns errors", async () => {
mockedNetwork.mockResolvedValueOnce(
getMockResponse({
errors: [{ message: "Database error" }],
}),
);
await expect(hgraphClient.getLatestIndexedConsensusTimestamp()).rejects.toThrow();
});
it("should throw error when no transactions found", async () => {
mockedNetwork.mockResolvedValueOnce(
getMockResponse({
data: {
ethereum_transaction: [],
},
}),
);
await expect(hgraphClient.getLatestIndexedConsensusTimestamp()).rejects.toThrow();
});
});
describe("getERC20Balances", () => {
beforeEach(() => {
jest.resetAllMocks();
});
it("should fetch and return ERC20 balances for an account", async () => {
const mockAddress = "0.0.1234";
const mockBalances = [
{
token_id: "0.0.5001",
balance: "1000000",
balance_timestamp: "1234567890.123456789",
created_timestamp: "1234567800.000000000",
},
{
token_id: "0.0.5002",
balance: "2000000",
balance_timestamp: "1234567890.123456789",
created_timestamp: "1234567800.000000000",
},
];
mockedNetwork.mockResolvedValueOnce(
getMockResponse({
data: {
erc_token_account: mockBalances,
},
}),
);
const result = await hgraphClient.getERC20Balances({ address: mockAddress });
const queryVariables = getRequestData(0).variables;
expect(mockedNetwork).toHaveBeenCalledTimes(1);
expect(queryVariables.accountId).toBe("1234");
expect(result).toEqual(mockBalances);
});
it("should extract account ID correctly from address", async () => {
mockedNetwork.mockResolvedValueOnce(
getMockResponse({
data: {
erc_token_account: [],
},
}),
);
await hgraphClient.getERC20Balances({ address: "0.0.9999" });
expect(getRequestData(0).variables.accountId).toBe("9999");
});
it("should throw error when API returns errors", async () => {
mockedNetwork.mockResolvedValueOnce(
getMockResponse({
errors: [{ message: "Account not found" }],
}),
);
await expect(hgraphClient.getERC20Balances({ address: "0.0.1234" })).rejects.toThrow();
});
});
describe("getERC20Transfers", () => {
beforeEach(() => {
jest.resetAllMocks();
});
it("should return empty array when no token addresses provided", async () => {
const result = await hgraphClient.getERC20Transfers({
address: "0.0.1234",
tokenEvmAddresses: [],
fetchAllPages: true,
});
expect(mockedNetwork).not.toHaveBeenCalled();
expect(result).toEqual([]);
});
it("should fetch transfers with default parameters", async () => {
const mockTransfer = {
token_id: "0.0.5001",
token_evm_address: "0xabc123",
sender_evm_address: "0x111",
sender_account_id: "0.0.1234",
receiver_evm_address: "0x222",
receiver_account_id: "0.0.5678",
payer_account_id: "0.0.1234",
amount: "1000",
transfer_type: "transfer",
consensus_timestamp: "1234567890123456789",
transaction_hash: "0xhash1",
};
mockedNetwork.mockResolvedValueOnce(
getMockResponse({
data: {
erc_token_transfer: [mockTransfer],
},
}),
);
const result = await hgraphClient.getERC20Transfers({
address: mockTransfer.payer_account_id,
tokenEvmAddresses: [mockTransfer.token_evm_address],
fetchAllPages: true,
});
const queryVariables = getRequestData(0).variables;
expect(mockedNetwork).toHaveBeenCalledTimes(1);
expect(result).toEqual([mockTransfer]);
expect(queryVariables.accountId).toBe("1234");
expect(queryVariables.limit).toBe(100);
});
it("should keep fetching all pages when fetchAllPages is true", async () => {
const mockTransfers1 = [
{ consensus_timestamp: "1000000000000000000", transaction_hash: "0xhash1" },
];
const mockTransfers2 = [
{ consensus_timestamp: "2000000000000000000", transaction_hash: "0xhash2" },
];
const mockTransfers3 = [
{ consensus_timestamp: "3000000000000000000", transaction_hash: "0xhash3" },
];
mockedNetwork
.mockResolvedValueOnce(
getMockResponse({
data: { erc_token_transfer: mockTransfers1 },
}),
)
.mockResolvedValueOnce(
getMockResponse({
data: { erc_token_transfer: mockTransfers2 },
}),
)
.mockResolvedValueOnce(
getMockResponse({
data: { erc_token_transfer: mockTransfers3 },
}),
)
.mockResolvedValueOnce(
getMockResponse({
data: { erc_token_transfer: [] },
}),
);
const result = await hgraphClient.getERC20Transfers({
address: "0.0.1234",
tokenEvmAddresses: ["0xabc123"],
limit: 1,
fetchAllPages: true,
});
expect(mockedNetwork).toHaveBeenCalledTimes(4);
expect(result.map(t => t.consensus_timestamp)).toEqual([
"1000000000000000000",
"2000000000000000000",
"3000000000000000000",
]);
});
it("should paginate when fetchAllPages is false", async () => {
const mockTransfers1 = [
{ consensus_timestamp: "1000000000000000000", transaction_hash: "0xhash1" },
{ consensus_timestamp: "1100000000000000000", transaction_hash: "0xhash1a" },
];
const mockTransfers2 = [
{ consensus_timestamp: "2000000000000000000", transaction_hash: "0xhash2" },
{ consensus_timestamp: "2100000000000000000", transaction_hash: "0xhash2a" },
];
mockedNetwork
.mockResolvedValueOnce(
getMockResponse({
data: { erc_token_transfer: mockTransfers1 },
}),
)
.mockResolvedValueOnce(
getMockResponse({
data: { erc_token_transfer: mockTransfers2 },
}),
);
const result = await hgraphClient.getERC20Transfers({
address: "0.0.1234",
tokenEvmAddresses: ["0xabc123"],
limit: 2,
fetchAllPages: false,
});
expect(mockedNetwork).toHaveBeenCalledTimes(1);
expect(result.map(t => t.consensus_timestamp)).toEqual([
"1000000000000000000",
"1100000000000000000",
]);
});
it("should handle timestamp parameter correctly", async () => {
mockedNetwork.mockResolvedValueOnce(
getMockResponse({
data: { erc_token_transfer: [] },
}),
);
await hgraphClient.getERC20Transfers({
address: "0.0.1234",
tokenEvmAddresses: ["0xabc123"],
timestamp: "1234567890.123456789",
fetchAllPages: true,
});
const query = getRequestData(0).query;
const queryVariables = getRequestData(0).variables;
expect(query).toContain("consensus_timestamp: { _gt: $cursor }");
expect(queryVariables.cursor).toBe("1234567890123456789");
});
it("should use correct pagination direction for desc order", async () => {
mockedNetwork.mockResolvedValueOnce(
getMockResponse({
data: { erc_token_transfer: [] },
}),
);
await hgraphClient.getERC20Transfers({
address: "0.0.1234",
tokenEvmAddresses: ["0xabc123"],
order: "desc",
fetchAllPages: false,
});
const query = getRequestData(0).query;
expect(query).toContain("order_by: { consensus_timestamp: desc }");
});
it("should throw error when API returns errors", async () => {
mockedNetwork.mockResolvedValueOnce(
getMockResponse({
errors: [{ message: "Query failed" }],
}),
);
await expect(
hgraphClient.getERC20Transfers({
address: "0.0.1234",
tokenEvmAddresses: ["0xabc123"],
fetchAllPages: true,
}),
).rejects.toThrow();
});
});
describe("getERC20TransfersByTimestampRange", () => {
beforeEach(() => {
jest.resetAllMocks();
});
it("should fetch transfers within timestamp range", async () => {
const mockTransfers = [
{
token_id: "0.0.5001",
consensus_timestamp: "1500000000000000000",
transaction_hash: "0xhash1",
},
];
mockedNetwork.mockResolvedValueOnce(
getMockResponse({
data: { erc_token_transfer: mockTransfers },
}),
);
const result = await hgraphClient.getERC20TransfersByTimestampRange({
startTimestamp: "1000.000000000",
endTimestamp: "2000.000000000",
});
const queryVariables = getRequestData(0).variables;
expect(mockedNetwork).toHaveBeenCalledTimes(1);
expect(result).toEqual(mockTransfers);
expect(queryVariables.startTimestamp).toBe("1000000000000");
expect(queryVariables.endTimestamp).toBe("2000000000000");
});
it("should normalize timestamps by removing dots", async () => {
mockedNetwork.mockResolvedValueOnce(
getMockResponse({
data: { erc_token_transfer: [] },
}),
);
await hgraphClient.getERC20TransfersByTimestampRange({
startTimestamp: "1234.567890123",
endTimestamp: "5678.901234567",
});
const queryVariables = getRequestData(0).variables;
expect(queryVariables.startTimestamp).toBe("1234567890123");
expect(queryVariables.endTimestamp).toBe("5678901234567");
});
it("should fetch all pages until no more results", async () => {
mockedNetwork
.mockResolvedValueOnce(
getMockResponse({
data: {
erc_token_transfer: [
{ consensus_timestamp: "1100000000000000000", transaction_hash: "0xhash1" },
],
},
}),
)
.mockResolvedValueOnce(
getMockResponse({
data: {
erc_token_transfer: [
{ consensus_timestamp: "1200000000000000000", transaction_hash: "0xhash2" },
],
},
}),
)
.mockResolvedValueOnce(
getMockResponse({
data: { erc_token_transfer: [] },
}),
);
const result = await hgraphClient.getERC20TransfersByTimestampRange({
startTimestamp: "1000.000000000",
endTimestamp: "2000.000000000",
limit: 1,
});
expect(mockedNetwork).toHaveBeenCalledTimes(3);
expect(result.map(t => t.consensus_timestamp)).toEqual([
"1100000000000000000",
"1200000000000000000",
]);
});
it("should use cursor for subsequent pages", async () => {
mockedNetwork
.mockResolvedValueOnce(
getMockResponse({
data: {
erc_token_transfer: [
{ consensus_timestamp: "1100000000000000000", transaction_hash: "0xhash1" },
],
},
}),
)
.mockResolvedValueOnce(
getMockResponse({
data: { erc_token_transfer: [] },
}),
);
await hgraphClient.getERC20TransfersByTimestampRange({
startTimestamp: "1000.000000000",
endTimestamp: "2000.000000000",
limit: 1,
});
const firstCall = mockedNetwork.mock.calls[0][0] as {
data: { query: string; variables: Record<string, unknown> };
};
const secondCall = mockedNetwork.mock.calls[1][0] as {
data: { query: string; variables: Record<string, unknown> };
};
// First call should use _gte with startTimestamp
expect(firstCall.data.query).toContain("_gte: $startTimestamp");
expect(firstCall.data.variables).not.toHaveProperty("cursor");
// Second call should use _gt with cursor
expect(secondCall.data.query).toContain("_gt: $cursor");
expect(secondCall.data.variables.cursor).toBe("1100000000000000000");
});
it("should support custom order parameter", async () => {
mockedNetwork.mockResolvedValueOnce(
getMockResponse({
data: { erc_token_transfer: [] },
}),
);
await hgraphClient.getERC20TransfersByTimestampRange({
startTimestamp: "1000.000000000",
endTimestamp: "2000.000000000",
order: "asc",
});
const query = getRequestData(0).query;
expect(query).toContain("order_by: { consensus_timestamp: asc }");
});
it("should throw error when API returns errors", async () => {
mockedNetwork.mockResolvedValueOnce(
getMockResponse({
errors: [{ message: "Invalid timestamp range" }],
}),
);
await expect(
hgraphClient.getERC20TransfersByTimestampRange({
startTimestamp: "1000.000000000",
endTimestamp: "2000.000000000",
}),
).rejects.toThrow();
});
});