@ledgerhq/coin-filecoin
Version:
Ledger Filecoin Coin integration
219 lines (179 loc) • 6.48 kB
text/typescript
import { getEnv } from "@ledgerhq/live-env";
import network from "@ledgerhq/live-network";
import {
createMockBalanceResponse,
createMockEstimatedFeesResponse,
createMockTransactionResponse,
createMockERC20Transfer,
TEST_ADDRESSES,
TEST_TRANSACTION_HASHES,
TEST_BLOCK_HEIGHTS,
} from "../test/fixtures";
import {
fetchBalances,
fetchEstimatedFees,
fetchBlockHeight,
fetchTxs,
fetchTxsWithPages,
broadcastTx,
fetchERC20TokenBalance,
fetchERC20TransactionsWithPages,
} from "../network/api";
// Mock dependencies
jest.mock("@ledgerhq/logs");
jest.mock("@ledgerhq/live-network/network");
jest.mock("@ledgerhq/live-env");
const MOCK_API_URL = "https://mock.filecoin.api";
const mockedNetwork = network as jest.MockedFunction<typeof network>;
const mockedGetEnv = getEnv as jest.MockedFunction<typeof getEnv>;
describe("Filecoin API", () => {
beforeEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
mockedGetEnv.mockReturnValue(MOCK_API_URL);
});
describe("fetchBalances", () => {
it("should fetch balance for a given address", async () => {
const mockBalance = createMockBalanceResponse({
total_balance: "1000000000000000000",
spendable_balance: "900000000000000000",
locked_balance: "100000000000000000",
});
mockedNetwork.mockResolvedValueOnce({ data: mockBalance, status: 200 });
const result = await fetchBalances(TEST_ADDRESSES.F1_ADDRESS);
expect(result).toEqual(mockBalance);
});
});
describe("fetchEstimatedFees", () => {
it("should fetch estimated fees for a transaction", async () => {
const mockFees = createMockEstimatedFeesResponse({
gas_limit: 1500000,
gas_fee_cap: "150000",
gas_premium: "125000",
nonce: 5,
});
const request = {
from: TEST_ADDRESSES.F1_ADDRESS,
to: TEST_ADDRESSES.RECIPIENT_F1,
};
mockedNetwork.mockResolvedValueOnce({ data: mockFees, status: 200 });
const result = await fetchEstimatedFees(request);
expect(result).toEqual(mockFees);
});
});
describe("fetchBlockHeight", () => {
it("should fetch current block height", async () => {
const mockNetworkStatus = {
current_block_identifier: {
index: TEST_BLOCK_HEIGHTS.CURRENT,
hash: "blockhash123",
},
genesis_block_identifier: {
index: 0,
hash: "genesis",
},
current_block_timestamp: Date.now(),
};
mockedNetwork.mockResolvedValueOnce({ data: mockNetworkStatus, status: 200 });
const result = await fetchBlockHeight();
expect(result.current_block_identifier.index).toBe(TEST_BLOCK_HEIGHTS.CURRENT);
});
});
describe("fetchTxs", () => {
it("should fetch transactions from specific height", async () => {
const mockResponse = {
txs: [createMockTransactionResponse()],
metadata: { limit: 50, offset: 10 },
};
mockedNetwork.mockResolvedValueOnce({ data: mockResponse, status: 200 });
await fetchTxs(TEST_ADDRESSES.F1_ADDRESS, 2500000, 10, 50);
expect(mockedNetwork).toHaveBeenCalledWith({
method: "GET",
url: expect.stringContaining("from_height=2500000&offset=10&limit=50"),
});
});
});
describe("fetchTxsWithPages", () => {
it("should fetch all transactions with multi-page pagination", async () => {
const firstPageTxs = Array.from({ length: 1000 }, (_, i) =>
createMockTransactionResponse({ hash: `hash_${i}` }),
);
const secondPageTxs = Array.from({ length: 500 }, (_, i) =>
createMockTransactionResponse({ hash: `hash_${1000 + i}` }),
);
mockedNetwork
.mockResolvedValueOnce({ data: { txs: firstPageTxs, metadata: {} }, status: 200 })
.mockResolvedValueOnce({ data: { txs: secondPageTxs, metadata: {} }, status: 200 });
const result = await fetchTxsWithPages(TEST_ADDRESSES.F1_ADDRESS, 0);
expect(result).toHaveLength(1500);
expect(mockedNetwork).toHaveBeenCalledTimes(2);
});
});
describe("broadcastTx", () => {
it("should broadcast a signed transaction", async () => {
const mockRequest = {
message: {
version: 0,
to: TEST_ADDRESSES.RECIPIENT_F1,
from: TEST_ADDRESSES.F1_ADDRESS,
nonce: 5,
value: "100000000000000000",
gaslimit: 1000000,
gasfeecap: "100000",
gaspremium: "100000",
method: 0,
params: "",
},
signature: {
type: 1,
data: "signature_data_here",
},
};
const mockResponse = {
hash: TEST_TRANSACTION_HASHES.VALID,
};
mockedNetwork.mockResolvedValueOnce({ data: mockResponse, status: 200 });
const result = await broadcastTx(mockRequest);
expect(result).toEqual({
hash: "bafy2bzacedpqzd6qm2r7nvxj5oetpqvhujwwmvkhz4u3xnfzdvwzxpjzuqhpa",
});
});
});
describe("fetchERC20TokenBalance", () => {
it("should return 0 when no balance data is available", async () => {
const mockResponse = {
data: [],
};
mockedNetwork.mockResolvedValueOnce({ data: mockResponse, status: 200 });
const result = await fetchERC20TokenBalance(
TEST_ADDRESSES.F4_ADDRESS,
TEST_ADDRESSES.ERC20_CONTRACT,
);
expect(result).toBe("0");
});
});
describe("fetchERC20TransactionsWithPages", () => {
it("should fetch all ERC20 transactions with pagination and sort by timestamp", async () => {
const now = Math.floor(Date.now() / 1000);
const firstPageTxs = Array.from({ length: 1000 }, (_, i) =>
createMockERC20Transfer({
id: `${i}`,
timestamp: now - i,
}),
);
const secondPageTxs = Array.from({ length: 300 }, (_, i) =>
createMockERC20Transfer({
id: `${1000 + i}`,
timestamp: now - 1000 - i,
}),
);
mockedNetwork
.mockResolvedValueOnce({ data: { txs: firstPageTxs }, status: 200 })
.mockResolvedValueOnce({ data: { txs: secondPageTxs }, status: 200 });
const result = await fetchERC20TransactionsWithPages(TEST_ADDRESSES.F4_ADDRESS, 100);
expect(result).toHaveLength(1300);
expect(mockedNetwork).toHaveBeenCalledTimes(2);
expect(result[0].timestamp).toBeGreaterThanOrEqual(result[1].timestamp);
});
});
});