@ledgerhq/coin-hedera 1.15.0-nightly.20251127023715 → 1.15.0-nightly.20251127130943
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -8
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.js +2 -6
- package/lib/api/index.js.map +1 -1
- package/lib/logic/getBlock.d.ts +3 -0
- package/lib/logic/getBlock.d.ts.map +1 -0
- package/lib/logic/getBlock.js +54 -0
- package/lib/logic/getBlock.js.map +1 -0
- package/lib/logic/getBlockInfo.d.ts +10 -0
- package/lib/logic/getBlockInfo.d.ts.map +1 -0
- package/lib/logic/getBlockInfo.js +24 -0
- package/lib/logic/getBlockInfo.js.map +1 -0
- package/lib/logic/index.d.ts +2 -0
- package/lib/logic/index.d.ts.map +1 -1
- package/lib/logic/index.js +5 -1
- package/lib/logic/index.js.map +1 -1
- package/lib/logic/utils.d.ts +13 -1
- package/lib/logic/utils.d.ts.map +1 -1
- package/lib/logic/utils.js +22 -2
- package/lib/logic/utils.js.map +1 -1
- package/lib/network/api.d.ts +2 -0
- package/lib/network/api.d.ts.map +1 -1
- package/lib/network/api.js +21 -0
- package/lib/network/api.js.map +1 -1
- package/lib-es/api/index.d.ts.map +1 -1
- package/lib-es/api/index.js +3 -7
- package/lib-es/api/index.js.map +1 -1
- package/lib-es/logic/getBlock.d.ts +3 -0
- package/lib-es/logic/getBlock.d.ts.map +1 -0
- package/lib-es/logic/getBlock.js +50 -0
- package/lib-es/logic/getBlock.js.map +1 -0
- package/lib-es/logic/getBlockInfo.d.ts +10 -0
- package/lib-es/logic/getBlockInfo.d.ts.map +1 -0
- package/lib-es/logic/getBlockInfo.js +20 -0
- package/lib-es/logic/getBlockInfo.js.map +1 -0
- package/lib-es/logic/index.d.ts +2 -0
- package/lib-es/logic/index.d.ts.map +1 -1
- package/lib-es/logic/index.js +2 -0
- package/lib-es/logic/index.js.map +1 -1
- package/lib-es/logic/utils.d.ts +13 -1
- package/lib-es/logic/utils.d.ts.map +1 -1
- package/lib-es/logic/utils.js +19 -1
- package/lib-es/logic/utils.js.map +1 -1
- package/lib-es/network/api.d.ts +2 -0
- package/lib-es/network/api.d.ts.map +1 -1
- package/lib-es/network/api.js +21 -0
- package/lib-es/network/api.js.map +1 -1
- package/package.json +7 -7
- package/src/api/index.integ.test.ts +117 -0
- package/src/api/index.ts +4 -8
- package/src/logic/getBlock.test.ts +101 -0
- package/src/logic/getBlock.ts +74 -0
- package/src/logic/getBlockInfo.test.ts +37 -0
- package/src/logic/getBlockInfo.ts +25 -0
- package/src/logic/index.ts +2 -0
- package/src/logic/utils.test.ts +80 -0
- package/src/logic/utils.ts +26 -2
- package/src/network/api.test.ts +121 -0
- package/src/network/api.ts +29 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AssetInfo,
|
|
3
|
+
Block,
|
|
4
|
+
BlockOperation,
|
|
5
|
+
BlockTransaction,
|
|
6
|
+
} from "@ledgerhq/coin-framework/api/types";
|
|
7
|
+
import { getBlockInfo } from "./getBlockInfo";
|
|
8
|
+
import { apiClient } from "../network/api";
|
|
9
|
+
import type { HederaMirrorCoinTransfer, HederaMirrorTokenTransfer } from "../types";
|
|
10
|
+
import { getTimestampRangeFromBlockHeight } from "./utils";
|
|
11
|
+
|
|
12
|
+
function toHederaAsset(
|
|
13
|
+
mirrorTransfer: HederaMirrorCoinTransfer | HederaMirrorTokenTransfer,
|
|
14
|
+
): AssetInfo {
|
|
15
|
+
if ("token_id" in mirrorTransfer) {
|
|
16
|
+
return {
|
|
17
|
+
type: "hts",
|
|
18
|
+
assetReference: mirrorTransfer.token_id,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return { type: "native" };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function toBlockOperation(
|
|
26
|
+
payerAccount: string,
|
|
27
|
+
chargedFee: number,
|
|
28
|
+
mirrorTransfer: HederaMirrorCoinTransfer | HederaMirrorTokenTransfer,
|
|
29
|
+
): BlockOperation {
|
|
30
|
+
const isTokenTransfer = "token_id" in mirrorTransfer;
|
|
31
|
+
const address = mirrorTransfer.account;
|
|
32
|
+
const asset = toHederaAsset(mirrorTransfer);
|
|
33
|
+
let amount = BigInt(mirrorTransfer.amount);
|
|
34
|
+
|
|
35
|
+
// exclude fee from payer's operation amount (fees are accounted for separately, so operations must not represent fees)
|
|
36
|
+
if (payerAccount === address && !isTokenTransfer) {
|
|
37
|
+
amount += BigInt(chargedFee);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
type: "transfer",
|
|
42
|
+
address,
|
|
43
|
+
asset,
|
|
44
|
+
amount,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function getBlock(height: number): Promise<Block> {
|
|
49
|
+
const { start, end } = getTimestampRangeFromBlockHeight(height);
|
|
50
|
+
const blockInfo = await getBlockInfo(height);
|
|
51
|
+
const transactions = await apiClient.getTransactionsByTimestampRange(start, end);
|
|
52
|
+
|
|
53
|
+
const blockTransactions: BlockTransaction[] = transactions.map(tx => {
|
|
54
|
+
const payerAccount = tx.transaction_id.split("-")[0];
|
|
55
|
+
const allTransfers = [...tx.transfers, ...tx.token_transfers];
|
|
56
|
+
|
|
57
|
+
const operations = allTransfers.map(transfer =>
|
|
58
|
+
toBlockOperation(payerAccount, tx.charged_tx_fee, transfer),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
hash: tx.transaction_hash,
|
|
63
|
+
failed: tx.result !== "SUCCESS",
|
|
64
|
+
operations,
|
|
65
|
+
fees: BigInt(tx.charged_tx_fee),
|
|
66
|
+
feesPayer: payerAccount,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
info: blockInfo,
|
|
72
|
+
transactions: blockTransactions,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SYNTHETIC_BLOCK_WINDOW_SECONDS } from "../constants";
|
|
2
|
+
import { getBlockInfo } from "./getBlockInfo";
|
|
3
|
+
|
|
4
|
+
describe("getBlockInfo", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
jest.clearAllMocks();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("should calculate time correctly based on block height and default window", async () => {
|
|
10
|
+
const blockHeight = 100;
|
|
11
|
+
const expectedSeconds = blockHeight * SYNTHETIC_BLOCK_WINDOW_SECONDS;
|
|
12
|
+
const expectedTime = new Date(expectedSeconds * 1000);
|
|
13
|
+
|
|
14
|
+
const result = await getBlockInfo(blockHeight);
|
|
15
|
+
|
|
16
|
+
expect(result).toMatchObject({
|
|
17
|
+
height: blockHeight,
|
|
18
|
+
hash: expect.any(String),
|
|
19
|
+
time: expectedTime,
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should use custom block window seconds when provided", async () => {
|
|
24
|
+
const blockHeight = 50;
|
|
25
|
+
const customWindow = 120;
|
|
26
|
+
const expectedSeconds = blockHeight * customWindow;
|
|
27
|
+
const expectedTime = new Date(expectedSeconds * 1000);
|
|
28
|
+
|
|
29
|
+
const result = await getBlockInfo(blockHeight, customWindow);
|
|
30
|
+
|
|
31
|
+
expect(result).toMatchObject({
|
|
32
|
+
height: blockHeight,
|
|
33
|
+
hash: expect.any(String),
|
|
34
|
+
time: expectedTime,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BlockInfo } from "@ledgerhq/coin-framework/lib-es/api/types";
|
|
2
|
+
import { SYNTHETIC_BLOCK_WINDOW_SECONDS } from "../constants";
|
|
3
|
+
import { getBlockHash } from "./utils";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Retrieves synthetic block information based on the provided block height.
|
|
7
|
+
*
|
|
8
|
+
* @param blockHeight - The height of the block for which to retrieve information.
|
|
9
|
+
* @param blockWindowSeconds - The duration in seconds that defines the synthetic block window (default is SYNTHETIC_BLOCK_WINDOW_SECONDS).
|
|
10
|
+
* @returns An object containing the block height, block hash, and block time.
|
|
11
|
+
*/
|
|
12
|
+
export const getBlockInfo = async (
|
|
13
|
+
blockHeight: number,
|
|
14
|
+
blockWindowSeconds = SYNTHETIC_BLOCK_WINDOW_SECONDS,
|
|
15
|
+
): Promise<BlockInfo> => {
|
|
16
|
+
const seconds = blockHeight * blockWindowSeconds;
|
|
17
|
+
const hash = getBlockHash(blockHeight);
|
|
18
|
+
const time = new Date(seconds * 1000);
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
height: blockHeight,
|
|
22
|
+
hash,
|
|
23
|
+
time,
|
|
24
|
+
};
|
|
25
|
+
};
|
package/src/logic/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { combine } from "./combine";
|
|
|
3
3
|
export { craftTransaction } from "./craftTransaction";
|
|
4
4
|
export { estimateFees } from "./estimateFees";
|
|
5
5
|
export { getBalance } from "./getBalance";
|
|
6
|
+
export { getBlock } from "./getBlock";
|
|
7
|
+
export { getBlockInfo } from "./getBlockInfo";
|
|
6
8
|
export { lastBlock } from "./lastBlock";
|
|
7
9
|
export { listOperations } from "./listOperations";
|
|
8
10
|
export { getAssetFromToken } from "./getAssetFromToken";
|
package/src/logic/utils.test.ts
CHANGED
|
@@ -33,6 +33,8 @@ import {
|
|
|
33
33
|
fromEVMAddress,
|
|
34
34
|
toEVMAddress,
|
|
35
35
|
formatTransactionId,
|
|
36
|
+
getTimestampRangeFromBlockHeight,
|
|
37
|
+
getBlockHash,
|
|
36
38
|
} from "./utils";
|
|
37
39
|
|
|
38
40
|
jest.mock("../network/api");
|
|
@@ -517,4 +519,82 @@ describe("logic utils", () => {
|
|
|
517
519
|
expect(fromEVMAddress(undefined as unknown as string)).toBeNull();
|
|
518
520
|
});
|
|
519
521
|
});
|
|
522
|
+
|
|
523
|
+
describe("getBlockHash", () => {
|
|
524
|
+
it("produces consistent 64-character hex hash", () => {
|
|
525
|
+
const hash = getBlockHash(12345);
|
|
526
|
+
|
|
527
|
+
expect(hash).toMatch(/^[0-9a-f]{64}$/);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("produces same hash for same block height", () => {
|
|
531
|
+
const hash1 = getBlockHash(100);
|
|
532
|
+
const hash2 = getBlockHash(100);
|
|
533
|
+
|
|
534
|
+
expect(hash1).toBe(hash2);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it("produces different hashes for different block heights", () => {
|
|
538
|
+
const hash1 = getBlockHash(100);
|
|
539
|
+
const hash2 = getBlockHash(101);
|
|
540
|
+
|
|
541
|
+
expect(hash1).not.toBe(hash2);
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
describe("getTimestampRangeFromBlockHeight", () => {
|
|
546
|
+
it("calculates consensus timestamp for block height 0 with default window", () => {
|
|
547
|
+
const result = getTimestampRangeFromBlockHeight(0);
|
|
548
|
+
|
|
549
|
+
expect(result).toEqual({
|
|
550
|
+
start: "0.000000000",
|
|
551
|
+
end: "10.000000000",
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
it("calculates consensus timestamp for block height 1 with default window", () => {
|
|
556
|
+
const result = getTimestampRangeFromBlockHeight(1);
|
|
557
|
+
|
|
558
|
+
expect(result).toEqual({
|
|
559
|
+
start: "10.000000000",
|
|
560
|
+
end: "20.000000000",
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it("calculates consensus timestamp with custom block window of 1 second", () => {
|
|
565
|
+
const result = getTimestampRangeFromBlockHeight(42, 1);
|
|
566
|
+
|
|
567
|
+
expect(result).toEqual({
|
|
568
|
+
start: "42.000000000",
|
|
569
|
+
end: "43.000000000",
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it("handles large block heights correctly", () => {
|
|
574
|
+
const result = getTimestampRangeFromBlockHeight(1000000);
|
|
575
|
+
|
|
576
|
+
expect(result).toEqual({
|
|
577
|
+
start: "10000000.000000000",
|
|
578
|
+
end: "10000010.000000000",
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it("ensures start and end timestamps are within the same block window", () => {
|
|
583
|
+
const blockHeight = 50;
|
|
584
|
+
const blockWindowSeconds = 10;
|
|
585
|
+
const result = getTimestampRangeFromBlockHeight(blockHeight, blockWindowSeconds);
|
|
586
|
+
|
|
587
|
+
const startSeconds = parseInt(result.start.split(".")[0]);
|
|
588
|
+
const endSeconds = parseInt(result.end.split(".")[0]);
|
|
589
|
+
|
|
590
|
+
expect(endSeconds - startSeconds).toBe(blockWindowSeconds);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it("maintains correct nanosecond precision format", () => {
|
|
594
|
+
const result = getTimestampRangeFromBlockHeight(123);
|
|
595
|
+
|
|
596
|
+
expect(result.start).toMatch(/^\d+\.000000000$/);
|
|
597
|
+
expect(result.end).toMatch(/^\d+\.000000000$/);
|
|
598
|
+
});
|
|
599
|
+
});
|
|
520
600
|
});
|
package/src/logic/utils.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
Transaction as HederaSDKTransaction,
|
|
8
8
|
TransactionId,
|
|
9
9
|
} from "@hashgraph/sdk";
|
|
10
|
-
import { AssetInfo, TransactionIntent } from "@ledgerhq/coin-framework/api/types";
|
|
10
|
+
import type { AssetInfo, TransactionIntent } from "@ledgerhq/coin-framework/api/types";
|
|
11
11
|
import { findCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
|
|
12
12
|
import { getFiatCurrencyByTicker } from "@ledgerhq/cryptoassets/fiats";
|
|
13
13
|
import cvsApi from "@ledgerhq/live-countervalues/api/index";
|
|
@@ -242,6 +242,10 @@ export const safeParseAccountId = (
|
|
|
242
242
|
}
|
|
243
243
|
};
|
|
244
244
|
|
|
245
|
+
export function getBlockHash(blockHeight: number): string {
|
|
246
|
+
return createHash("sha256").update(blockHeight.toString()).digest("hex");
|
|
247
|
+
}
|
|
248
|
+
|
|
245
249
|
/**
|
|
246
250
|
* Calculates a synthetic block height based on Hedera consensus timestamp.
|
|
247
251
|
*
|
|
@@ -260,12 +264,32 @@ export function getSyntheticBlock(
|
|
|
260
264
|
}
|
|
261
265
|
|
|
262
266
|
const blockHeight = Math.floor(seconds / blockWindowSeconds);
|
|
263
|
-
const blockHash =
|
|
267
|
+
const blockHash = getBlockHash(blockHeight);
|
|
264
268
|
const blockTime = new Date(seconds * 1000);
|
|
265
269
|
|
|
266
270
|
return { blockHeight, blockHash, blockTime };
|
|
267
271
|
}
|
|
268
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Calculates the timestamp range based on a synthetic block height.
|
|
275
|
+
*
|
|
276
|
+
* @param blockHeight - The synthetic block height
|
|
277
|
+
* @param blockWindowSeconds - Duration of one synthetic block in seconds (default: 10)
|
|
278
|
+
* @returns Hedera timestamp range as a string
|
|
279
|
+
*/
|
|
280
|
+
export function getTimestampRangeFromBlockHeight(
|
|
281
|
+
blockHeight: number,
|
|
282
|
+
blockWindowSeconds = SYNTHETIC_BLOCK_WINDOW_SECONDS,
|
|
283
|
+
) {
|
|
284
|
+
const startTimestamp = blockHeight * blockWindowSeconds;
|
|
285
|
+
const endTimestamp = (blockHeight + 1) * blockWindowSeconds;
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
start: `${startTimestamp}.000000000`,
|
|
289
|
+
end: `${endTimestamp}.000000000`,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
269
293
|
export const formatTransactionId = (transactionId: TransactionId): string => {
|
|
270
294
|
const [accountId, timestamp] = transactionId.toString().split("@");
|
|
271
295
|
const [secs, nanos] = timestamp.split(".");
|
package/src/network/api.test.ts
CHANGED
|
@@ -400,3 +400,124 @@ describe("estimateContractCallGas", () => {
|
|
|
400
400
|
expect(mockedNetwork).toHaveBeenCalledTimes(1);
|
|
401
401
|
});
|
|
402
402
|
});
|
|
403
|
+
|
|
404
|
+
describe("getTransactionsByTimestampRange", () => {
|
|
405
|
+
beforeEach(() => {
|
|
406
|
+
jest.resetAllMocks();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("should include correct query params with timestamp range", async () => {
|
|
410
|
+
mockedNetwork.mockResolvedValueOnce(
|
|
411
|
+
getMockResponse({ transactions: [], links: { next: null } }),
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
await apiClient.getTransactionsByTimestampRange("1000.000000000", "2000.000000000");
|
|
415
|
+
|
|
416
|
+
const requestUrl = mockedNetwork.mock.calls[0][0].url;
|
|
417
|
+
expect(requestUrl).toContain("timestamp=gte%3A1000.000000000");
|
|
418
|
+
expect(requestUrl).toContain("timestamp=lt%3A2000.000000000");
|
|
419
|
+
expect(requestUrl).toContain("limit=100");
|
|
420
|
+
expect(requestUrl).toContain("order=desc");
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it("should return empty array when no transactions found", async () => {
|
|
424
|
+
mockedNetwork.mockResolvedValueOnce(
|
|
425
|
+
getMockResponse({ transactions: [], links: { next: null } }),
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
const result = await apiClient.getTransactionsByTimestampRange(
|
|
429
|
+
"1000.000000000",
|
|
430
|
+
"2000.000000000",
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
expect(result).toEqual([]);
|
|
434
|
+
expect(mockedNetwork).toHaveBeenCalledTimes(1);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it("should return all transactions when only one page is needed", async () => {
|
|
438
|
+
mockedNetwork.mockResolvedValueOnce(
|
|
439
|
+
getMockResponse({
|
|
440
|
+
transactions: [
|
|
441
|
+
{ consensus_timestamp: "1500.123456789" },
|
|
442
|
+
{ consensus_timestamp: "1750.987654321" },
|
|
443
|
+
],
|
|
444
|
+
links: { next: null },
|
|
445
|
+
}),
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
const result = await apiClient.getTransactionsByTimestampRange(
|
|
449
|
+
"1000.000000000",
|
|
450
|
+
"2000.000000000",
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
expect(result).toHaveLength(2);
|
|
454
|
+
expect(result.map(tx => tx.consensus_timestamp)).toEqual(["1500.123456789", "1750.987654321"]);
|
|
455
|
+
expect(mockedNetwork).toHaveBeenCalledTimes(1);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it("should keep fetching all pages when links.next is present", async () => {
|
|
459
|
+
mockedNetwork
|
|
460
|
+
.mockResolvedValueOnce(
|
|
461
|
+
getMockResponse({
|
|
462
|
+
transactions: [{ consensus_timestamp: "1100.000000000" }],
|
|
463
|
+
links: { next: "/next-1" },
|
|
464
|
+
}),
|
|
465
|
+
)
|
|
466
|
+
.mockResolvedValueOnce(
|
|
467
|
+
getMockResponse({
|
|
468
|
+
transactions: [{ consensus_timestamp: "1200.000000000" }],
|
|
469
|
+
links: { next: "/next-2" },
|
|
470
|
+
}),
|
|
471
|
+
)
|
|
472
|
+
.mockResolvedValueOnce(
|
|
473
|
+
getMockResponse({
|
|
474
|
+
transactions: [{ consensus_timestamp: "1300.000000000" }],
|
|
475
|
+
links: { next: null },
|
|
476
|
+
}),
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
const result = await apiClient.getTransactionsByTimestampRange(
|
|
480
|
+
"1000.000000000",
|
|
481
|
+
"2000.000000000",
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
expect(result).toHaveLength(3);
|
|
485
|
+
expect(result.map(tx => tx.consensus_timestamp)).toEqual([
|
|
486
|
+
"1100.000000000",
|
|
487
|
+
"1200.000000000",
|
|
488
|
+
"1300.000000000",
|
|
489
|
+
]);
|
|
490
|
+
expect(mockedNetwork).toHaveBeenCalledTimes(3);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it("should handle empty pages and continue fetching", async () => {
|
|
494
|
+
mockedNetwork
|
|
495
|
+
.mockResolvedValueOnce(
|
|
496
|
+
getMockResponse({
|
|
497
|
+
transactions: [{ consensus_timestamp: "1100.000000000" }],
|
|
498
|
+
links: { next: "/next-1" },
|
|
499
|
+
}),
|
|
500
|
+
)
|
|
501
|
+
.mockResolvedValueOnce(
|
|
502
|
+
getMockResponse({
|
|
503
|
+
transactions: [],
|
|
504
|
+
links: { next: "/next-2" },
|
|
505
|
+
}),
|
|
506
|
+
)
|
|
507
|
+
.mockResolvedValueOnce(
|
|
508
|
+
getMockResponse({
|
|
509
|
+
transactions: [{ consensus_timestamp: "1300.000000000" }],
|
|
510
|
+
links: { next: null },
|
|
511
|
+
}),
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
const result = await apiClient.getTransactionsByTimestampRange(
|
|
515
|
+
"1000.000000000",
|
|
516
|
+
"2000.000000000",
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
expect(result).toHaveLength(2);
|
|
520
|
+
expect(result.map(tx => tx.consensus_timestamp)).toEqual(["1100.000000000", "1300.000000000"]);
|
|
521
|
+
expect(mockedNetwork).toHaveBeenCalledTimes(3);
|
|
522
|
+
});
|
|
523
|
+
});
|
package/src/network/api.ts
CHANGED
|
@@ -239,6 +239,34 @@ async function estimateContractCallGas(
|
|
|
239
239
|
return new BigNumber(res.data.result);
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
async function getTransactionsByTimestampRange(
|
|
243
|
+
startTimestamp: string,
|
|
244
|
+
endTimestamp: string,
|
|
245
|
+
): Promise<HederaMirrorTransaction[]> {
|
|
246
|
+
const transactions: HederaMirrorTransaction[] = [];
|
|
247
|
+
const params = new URLSearchParams({
|
|
248
|
+
limit: "100",
|
|
249
|
+
order: "desc",
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
params.append("timestamp", `gte:${startTimestamp}`);
|
|
253
|
+
params.append("timestamp", `lt:${endTimestamp}`);
|
|
254
|
+
|
|
255
|
+
let nextPath: string | null = `/api/v1/transactions?${params.toString()}`;
|
|
256
|
+
|
|
257
|
+
while (nextPath) {
|
|
258
|
+
const res: LiveNetworkResponse<HederaMirrorTransactionsResponse> = await network({
|
|
259
|
+
method: "GET",
|
|
260
|
+
url: `${API_URL}${nextPath}`,
|
|
261
|
+
});
|
|
262
|
+
const newTransactions = res.data.transactions;
|
|
263
|
+
transactions.push(...newTransactions);
|
|
264
|
+
nextPath = res.data.links.next;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return transactions;
|
|
268
|
+
}
|
|
269
|
+
|
|
242
270
|
export const apiClient = {
|
|
243
271
|
getAccountsForPublicKey,
|
|
244
272
|
getAccount,
|
|
@@ -250,4 +278,5 @@ export const apiClient = {
|
|
|
250
278
|
findTransactionByContractCall,
|
|
251
279
|
getERC20Balance,
|
|
252
280
|
estimateContractCallGas,
|
|
281
|
+
getTransactionsByTimestampRange,
|
|
253
282
|
};
|