@ledgerhq/coin-tron 4.0.0-next.0 → 4.1.0-next.0

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.
Files changed (60) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +26 -0
  3. package/lib/api/index.d.ts.map +1 -1
  4. package/lib/api/index.js +6 -1
  5. package/lib/api/index.js.map +1 -1
  6. package/lib/api/index.test.js +7 -2
  7. package/lib/api/index.test.js.map +1 -1
  8. package/lib/logic/listOperations.d.ts +7 -1
  9. package/lib/logic/listOperations.d.ts.map +1 -1
  10. package/lib/logic/listOperations.integ.test.js +56 -4
  11. package/lib/logic/listOperations.integ.test.js.map +1 -1
  12. package/lib/logic/listOperations.js +20 -7
  13. package/lib/logic/listOperations.js.map +1 -1
  14. package/lib/logic/listOperations.unit.test.js +5 -3
  15. package/lib/logic/listOperations.unit.test.js.map +1 -1
  16. package/lib/network/format.js +2 -2
  17. package/lib/network/format.js.map +1 -1
  18. package/lib/network/index.d.ts +5 -1
  19. package/lib/network/index.d.ts.map +1 -1
  20. package/lib/network/index.js +56 -5
  21. package/lib/network/index.js.map +1 -1
  22. package/lib/network/index.test.js +93 -28
  23. package/lib/network/index.test.js.map +1 -1
  24. package/lib/network/types.fixture.d.ts +384 -48
  25. package/lib/network/types.fixture.d.ts.map +1 -1
  26. package/lib/network/types.fixture.js.map +1 -1
  27. package/lib-es/api/index.d.ts.map +1 -1
  28. package/lib-es/api/index.js +6 -1
  29. package/lib-es/api/index.js.map +1 -1
  30. package/lib-es/api/index.test.js +7 -2
  31. package/lib-es/api/index.test.js.map +1 -1
  32. package/lib-es/logic/listOperations.d.ts +7 -1
  33. package/lib-es/logic/listOperations.d.ts.map +1 -1
  34. package/lib-es/logic/listOperations.integ.test.js +57 -5
  35. package/lib-es/logic/listOperations.integ.test.js.map +1 -1
  36. package/lib-es/logic/listOperations.js +19 -7
  37. package/lib-es/logic/listOperations.js.map +1 -1
  38. package/lib-es/logic/listOperations.unit.test.js +6 -4
  39. package/lib-es/logic/listOperations.unit.test.js.map +1 -1
  40. package/lib-es/network/format.js +2 -2
  41. package/lib-es/network/format.js.map +1 -1
  42. package/lib-es/network/index.d.ts +5 -1
  43. package/lib-es/network/index.d.ts.map +1 -1
  44. package/lib-es/network/index.js +56 -5
  45. package/lib-es/network/index.js.map +1 -1
  46. package/lib-es/network/index.test.js +93 -28
  47. package/lib-es/network/index.test.js.map +1 -1
  48. package/lib-es/network/types.fixture.d.ts +384 -48
  49. package/lib-es/network/types.fixture.d.ts.map +1 -1
  50. package/lib-es/network/types.fixture.js.map +1 -1
  51. package/package.json +4 -4
  52. package/src/api/index.test.ts +7 -2
  53. package/src/api/index.ts +7 -1
  54. package/src/logic/listOperations.integ.test.ts +74 -5
  55. package/src/logic/listOperations.ts +31 -7
  56. package/src/logic/listOperations.unit.test.ts +8 -4
  57. package/src/network/format.ts +2 -2
  58. package/src/network/index.test.ts +120 -27
  59. package/src/network/index.ts +80 -9
  60. package/src/network/types.fixture.ts +1 -1
@@ -1,33 +1,33 @@
1
1
  import { HttpResponse, http } from "msw";
2
- import { setupServer } from "msw/node";
2
+ import { setupServer, SetupServerApi } from "msw/node";
3
3
  import { TRANSACTION_DETAIL_FIXTURE, TRANSACTION_FIXTURE, TRC20_FIXTURE } from "./types.fixture";
4
4
  import coinConfig from "../config";
5
5
  import { defaultFetchParams, fetchTronAccountTxs } from ".";
6
+ import { assert } from "console";
6
7
 
7
8
  const TRON_BASE_URL_TEST = "https://httpbin.org";
8
9
 
9
- const handlers = [
10
- http.get(`${TRON_BASE_URL_TEST}/v1/accounts/:addr/transactions`, () => {
11
- // const url = new URL(request.url);
12
- // const _ = url.searchParams.get("get_detail");
13
- return HttpResponse.json(TRANSACTION_FIXTURE);
14
- }),
15
- http.get(`${TRON_BASE_URL_TEST}/v1/accounts/:addr/transactions/trc20`, () => {
16
- // const url = new URL(request.url);
17
- // const _ = url.searchParams.get("get_detail");
18
- return HttpResponse.json(TRC20_FIXTURE);
19
- }),
20
- http.get(`${TRON_BASE_URL_TEST}/wallet/gettransactioninfobyid`, ({ request }) => {
10
+ const defaultGetTransactionsH = http.get(
11
+ `${TRON_BASE_URL_TEST}/v1/accounts/:addr/transactions`,
12
+ () => HttpResponse.json(TRANSACTION_FIXTURE),
13
+ );
14
+
15
+ const defaultGetTrc20TransactionsH = http.get(
16
+ `${TRON_BASE_URL_TEST}/v1/accounts/:addr/transactions/trc20`,
17
+ () => HttpResponse.json(TRC20_FIXTURE),
18
+ );
19
+
20
+ const defaultGetTxInfo = http.get(
21
+ `${TRON_BASE_URL_TEST}/wallet/gettransactioninfobyid`,
22
+ ({ request }) => {
21
23
  const url = new URL(request.url);
22
24
  const value = url.searchParams.get("value") ?? "UNKNOWN";
23
25
  return HttpResponse.json(TRANSACTION_DETAIL_FIXTURE(value));
24
- }),
25
- ];
26
+ },
27
+ );
26
28
 
27
- const mockServer = setupServer(...handlers);
28
-
29
- describe("fetchTronAccountTxs", () => {
30
- beforeAll(() => {
29
+ function doBeforeAll(server: SetupServerApi): () => void {
30
+ return () => {
31
31
  coinConfig.setCoinConfig(() => ({
32
32
  status: {
33
33
  type: "active",
@@ -37,16 +37,26 @@ describe("fetchTronAccountTxs", () => {
37
37
  },
38
38
  }));
39
39
 
40
- mockServer.listen();
41
- });
40
+ server.listen();
41
+ };
42
+ }
42
43
 
43
- beforeEach(() => {
44
- mockServer.resetHandlers();
45
- });
44
+ function doBeforeEach(server: SetupServerApi): () => void {
45
+ return () => server.resetHandlers();
46
+ }
46
47
 
47
- afterAll(() => {
48
- mockServer.close();
49
- });
48
+ function doAfterAll(server: SetupServerApi): () => void {
49
+ return () => server.close();
50
+ }
51
+
52
+ describe("fetchTronAccountTxs", () => {
53
+ const handlers = [defaultGetTransactionsH, defaultGetTrc20TransactionsH, defaultGetTxInfo];
54
+
55
+ const mockServer = setupServer(...handlers);
56
+
57
+ beforeAll(doBeforeAll(mockServer));
58
+ beforeEach(doBeforeEach(mockServer));
59
+ afterAll(doAfterAll(mockServer));
50
60
 
51
61
  it("convert correctly operations from the blockchain", async () => {
52
62
  // WHEN
@@ -64,3 +74,86 @@ describe("fetchTronAccountTxs", () => {
64
74
  expect(tx!.to).toEqual("TAVrrARNdnjHgCGMQYeQV7hv4PSu7mVsMj");
65
75
  });
66
76
  });
77
+
78
+ describe("fetchTronAccountTxs with invalid TRC20 (see LIVE-18992)", () => {
79
+ const tx1Hash = "1237889e91c0ebbe389436c341865df09921f8f0c029d9286102372cbaadc585";
80
+ const tx2Hash = "154164dd04482ae78f930033d0ad95730b8b19fde171a33c3920d18c228426ab";
81
+ let counterGetTrc20 = 0;
82
+ const invalidTrc20Handler = http.get(
83
+ `${TRON_BASE_URL_TEST}/v1/accounts/:addr/transactions/trc20`,
84
+ () => {
85
+ const ret: any = JSON.parse(JSON.stringify(TRC20_FIXTURE));
86
+ switch (counterGetTrc20) {
87
+ case 0: {
88
+ const tx1 = ret.data[0];
89
+ assert(tx1.transaction_id == tx1Hash);
90
+ ret.data[0].detail.ret = undefined;
91
+ break;
92
+ }
93
+ case 1: {
94
+ const tx2 = ret.data[1];
95
+ assert(tx2.transaction_id == tx2Hash);
96
+ ret.data[1].detail.ret = undefined;
97
+ break;
98
+ }
99
+ default:
100
+ // the 3rd call should not happen
101
+ // because merging the 1st and 2nd results is enough to have a full set, perfectly well formed
102
+ throw "results should be merged after 2 calls";
103
+ }
104
+ counterGetTrc20++;
105
+ return HttpResponse.json(ret);
106
+ },
107
+ );
108
+ const handlers = [defaultGetTransactionsH, invalidTrc20Handler, defaultGetTxInfo];
109
+ const mockServer = setupServer(...handlers);
110
+
111
+ beforeAll(doBeforeAll(mockServer));
112
+ beforeEach(doBeforeEach(mockServer));
113
+ afterAll(doAfterAll(mockServer));
114
+
115
+ it("retry several times until result is correct", async () => {
116
+ // WHEN
117
+ const results = await fetchTronAccountTxs("ADDRESS", () => true, {}, defaultFetchParams);
118
+
119
+ // THEN
120
+ const tx1 = results.find(tx => tx.txID === tx1Hash);
121
+ expect(tx1).toBeDefined();
122
+ const tx2 = results.find(tx => tx.txID === tx2Hash);
123
+ expect(tx2).toBeDefined();
124
+ });
125
+ });
126
+
127
+ describe("fetchTronAccountTxs with invalid TRC20 (see LIVE-18992): after 3 tries it throws an exception", () => {
128
+ const tx1Hash = "1237889e91c0ebbe389436c341865df09921f8f0c029d9286102372cbaadc585";
129
+ const alwaysInvalidTrc20Handler = http.get(
130
+ `${TRON_BASE_URL_TEST}/v1/accounts/:addr/transactions/trc20`,
131
+ () => {
132
+ const ret: any = JSON.parse(JSON.stringify(TRC20_FIXTURE));
133
+ const tx1 = ret.data[0];
134
+ assert(tx1.transaction_id == tx1Hash);
135
+ ret.data[0].detail.ret = undefined;
136
+ return HttpResponse.json(ret);
137
+ },
138
+ );
139
+
140
+ const handlers = [defaultGetTransactionsH, alwaysInvalidTrc20Handler, defaultGetTxInfo];
141
+ const mockServer = setupServer(...handlers);
142
+
143
+ beforeAll(doBeforeAll(mockServer));
144
+ beforeEach(doBeforeEach(mockServer));
145
+ afterAll(doAfterAll(mockServer));
146
+
147
+ it("after several retry, it gives up on retry", async () => {
148
+ try {
149
+ await fetchTronAccountTxs("ADDRESS", () => true, {}, defaultFetchParams);
150
+ } catch (e) {
151
+ expect(e).toBeDefined();
152
+ expect((e as Error).message).toBe(
153
+ "getTrc20TxsWithRetry: couldn't fetch trc20 transactions after several attempts",
154
+ );
155
+ return;
156
+ }
157
+ fail("should have thrown an error");
158
+ });
159
+ });
@@ -483,13 +483,18 @@ export type FetchTxsStopPredicate = (
483
483
  ) => boolean;
484
484
 
485
485
  export type FetchParams = {
486
- limit: number;
486
+ /** The maximum number of transactions to fetch per call. */
487
+ limitPerCall: number;
488
+ /** Hint about the number of transactions to be fetched in total (hint to optimize `limitPerCall`) */
489
+ hintGlobalLimit?: number;
487
490
  minTimestamp: number;
491
+ order: "asc" | "desc";
488
492
  };
489
493
 
490
494
  export const defaultFetchParams: FetchParams = {
491
- limit: 100,
495
+ limitPerCall: 100,
492
496
  minTimestamp: 0,
497
+ order: "desc",
493
498
  } as const;
494
499
 
495
500
  export async function fetchTronAccountTxs(
@@ -498,12 +503,15 @@ export async function fetchTronAccountTxs(
498
503
  cacheTransactionInfoById: Record<string, TronTransactionInfo>,
499
504
  params: FetchParams,
500
505
  ): Promise<TrongridTxInfo[]> {
501
- const queryParamsNativeTxs = `limit=${params.limit}&min_timestamp=${params.minTimestamp}`;
506
+ const adjustedLimitPerCall = params.hintGlobalLimit
507
+ ? Math.min(params.limitPerCall, params.hintGlobalLimit)
508
+ : params.limitPerCall;
509
+ const queryParams = `limit=${adjustedLimitPerCall}&min_timestamp=${params.minTimestamp}&order_by=block_timestamp,${params.order}`;
502
510
  const nativeTxs = (
503
511
  await getAllTransactions<
504
512
  (TransactionTronAPI & { detail?: TronTransactionInfo }) | MalformedTransactionTronAPI
505
513
  >(
506
- `${getBaseApiUrl()}/v1/accounts/${addr}/transactions?${queryParamsNativeTxs}`,
514
+ `${getBaseApiUrl()}/v1/accounts/${addr}/transactions?${queryParams}`,
507
515
  shouldFetchMoreTxs,
508
516
  getTransactions(cacheTransactionInfoById),
509
517
  )
@@ -528,14 +536,77 @@ export async function fetchTronAccountTxs(
528
536
 
529
537
  // we need to fetch and filter trc20 transactions from another endpoint
530
538
  // doc https://developers.tron.network/reference/get-trc20-transaction-info-by-account-address
531
- const queryParamsTrc20Txs = `limit=${params.limit}&min_timestamp=${params.minTimestamp}`;
532
- const trc20Txs = (
539
+
540
+ const callTrc20Endpoint = async () =>
533
541
  await getAllTransactions<Trc20API>(
534
- `${getBaseApiUrl()}/v1/accounts/${addr}/transactions/trc20?${queryParamsTrc20Txs}&get_detail=true`,
542
+ `${getBaseApiUrl()}/v1/accounts/${addr}/transactions/trc20?${queryParams}&get_detail=true`,
535
543
  shouldFetchMoreTxs,
536
544
  getTrc20,
537
- )
538
- ).map(tx => formatTrongridTrc20TxResponse(tx));
545
+ );
546
+
547
+ type Acc = {
548
+ txs: Trc20API[];
549
+ invalids: number[];
550
+ };
551
+
552
+ function isValid(tx: Trc20API): boolean {
553
+ const ret = tx?.detail?.ret;
554
+ return Array.isArray(ret) && ret.length > 0;
555
+ }
556
+
557
+ function getInvalidTxIndexes(txs: Trc20API[]): number[] {
558
+ const invalids: number[] = [];
559
+ for (let i = 0; i < txs.length; i++) {
560
+ if (!isValid(txs[i])) {
561
+ invalids.push(i);
562
+ }
563
+ }
564
+ txs.filter(tx => !isValid(tx)).map((tx, index) => index);
565
+ return invalids;
566
+ }
567
+
568
+ function assert(predicate: boolean, message: string) {
569
+ if (!predicate) {
570
+ throw new Error(message);
571
+ }
572
+ }
573
+
574
+ // Merge the two results
575
+ function mergeAccs(acc1: Acc, acc2: Acc): Acc {
576
+ assert(acc1.txs.length == acc2.txs.length, "accs should have the same length");
577
+ const accRet: Acc = { txs: acc1.txs, invalids: [] };
578
+ acc1.invalids.forEach(invalidIndex => {
579
+ acc2.invalids.includes(invalidIndex)
580
+ ? accRet.invalids.push(invalidIndex)
581
+ : (accRet.txs[invalidIndex] = acc2.txs[invalidIndex]);
582
+ });
583
+ return accRet;
584
+ }
585
+
586
+ // see LIVE-18992 for an explanation to why we need this
587
+ async function getTrc20TxsWithRetry(acc: Acc | null, times: number): Promise<Trc20API[]> {
588
+ assert(
589
+ times > 0,
590
+ "getTrc20TxsWithRetry: couldn't fetch trc20 transactions after several attempts",
591
+ );
592
+ const ret = await callTrc20Endpoint();
593
+ const thisAcc: Acc = {
594
+ txs: ret,
595
+ invalids: getInvalidTxIndexes(ret),
596
+ };
597
+ const newAcc = acc ? mergeAccs(acc, thisAcc) : thisAcc;
598
+ if (newAcc.invalids.length == 0) {
599
+ return newAcc.txs;
600
+ } else {
601
+ log(
602
+ "coin-tron",
603
+ `getTrc20TxsWithRetry: got ${newAcc.invalids.length} invalid trc20 transactions, retrying...`,
604
+ );
605
+ return await getTrc20TxsWithRetry(newAcc, times - 1);
606
+ }
607
+ }
608
+
609
+ const trc20Txs = (await getTrc20TxsWithRetry(null, 3)).map(formatTrongridTrc20TxResponse);
539
610
 
540
611
  const txInfos: TrongridTxInfo[] = compact(nativeTxs.concat(trc20Txs)).sort(
541
612
  (a, b) => b.date.getTime() - a.date.getTime(),
@@ -4434,7 +4434,7 @@ export const TRC20_FIXTURE = {
4434
4434
  at: 1718032314555,
4435
4435
  page_size: 8,
4436
4436
  },
4437
- };
4437
+ } as const;
4438
4438
 
4439
4439
  export const TRANSACTION_DETAIL_FIXTURE = (id: string) => ({
4440
4440
  id,