@ledgerhq/coin-hedera 1.6.7-nightly.1 → 1.6.7

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 (66) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.unimportedrc.json +0 -1
  3. package/CHANGELOG.md +20 -6
  4. package/lib/api/mirror.d.ts +3 -16
  5. package/lib/api/mirror.d.ts.map +1 -1
  6. package/lib/api/mirror.js +10 -29
  7. package/lib/api/mirror.js.map +1 -1
  8. package/lib/bridge/broadcast.d.ts.map +1 -1
  9. package/lib/bridge/broadcast.js +1 -7
  10. package/lib/bridge/broadcast.js.map +1 -1
  11. package/lib/bridge/synchronisation.js +3 -3
  12. package/lib/bridge/synchronisation.js.map +1 -1
  13. package/lib/bridge/utils.d.ts +2 -3
  14. package/lib/bridge/utils.d.ts.map +1 -1
  15. package/lib/bridge/utils.js +1 -10
  16. package/lib/bridge/utils.js.map +1 -1
  17. package/lib/types/bridge.d.ts +3 -1
  18. package/lib/types/bridge.d.ts.map +1 -1
  19. package/lib-es/api/mirror.d.ts +3 -16
  20. package/lib-es/api/mirror.d.ts.map +1 -1
  21. package/lib-es/api/mirror.js +9 -27
  22. package/lib-es/api/mirror.js.map +1 -1
  23. package/lib-es/bridge/broadcast.d.ts.map +1 -1
  24. package/lib-es/bridge/broadcast.js +2 -8
  25. package/lib-es/bridge/broadcast.js.map +1 -1
  26. package/lib-es/bridge/synchronisation.js +3 -3
  27. package/lib-es/bridge/synchronisation.js.map +1 -1
  28. package/lib-es/bridge/utils.d.ts +2 -3
  29. package/lib-es/bridge/utils.d.ts.map +1 -1
  30. package/lib-es/bridge/utils.js +0 -8
  31. package/lib-es/bridge/utils.js.map +1 -1
  32. package/lib-es/types/bridge.d.ts +3 -1
  33. package/lib-es/types/bridge.d.ts.map +1 -1
  34. package/package.json +10 -11
  35. package/src/api/mirror.ts +19 -43
  36. package/src/bridge/broadcast.ts +4 -11
  37. package/src/bridge/synchronisation.ts +3 -3
  38. package/src/bridge/utils.ts +2 -14
  39. package/src/types/bridge.ts +4 -1
  40. package/lib/api/mirror.test.d.ts +0 -2
  41. package/lib/api/mirror.test.d.ts.map +0 -1
  42. package/lib/api/mirror.test.js +0 -60
  43. package/lib/api/mirror.test.js.map +0 -1
  44. package/lib/logic.d.ts +0 -5
  45. package/lib/logic.d.ts.map +0 -1
  46. package/lib/logic.js +0 -9
  47. package/lib/logic.js.map +0 -1
  48. package/lib/logic.test.d.ts +0 -2
  49. package/lib/logic.test.d.ts.map +0 -1
  50. package/lib/logic.test.js +0 -54
  51. package/lib/logic.test.js.map +0 -1
  52. package/lib-es/api/mirror.test.d.ts +0 -2
  53. package/lib-es/api/mirror.test.d.ts.map +0 -1
  54. package/lib-es/api/mirror.test.js +0 -55
  55. package/lib-es/api/mirror.test.js.map +0 -1
  56. package/lib-es/logic.d.ts +0 -5
  57. package/lib-es/logic.d.ts.map +0 -1
  58. package/lib-es/logic.js +0 -6
  59. package/lib-es/logic.js.map +0 -1
  60. package/lib-es/logic.test.d.ts +0 -2
  61. package/lib-es/logic.test.d.ts.map +0 -1
  62. package/lib-es/logic.test.js +0 -52
  63. package/lib-es/logic.test.js.map +0 -1
  64. package/src/api/mirror.test.ts +0 -76
  65. package/src/logic.test.ts +0 -58
  66. package/src/logic.ts +0 -15
package/src/api/mirror.ts CHANGED
@@ -6,7 +6,6 @@ import { getEnv } from "@ledgerhq/live-env";
6
6
  import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
7
7
  import { getAccountBalance } from "./network";
8
8
  import { base64ToUrlSafeBase64 } from "../bridge/utils";
9
- import { HederaOperationExtra } from "../types";
10
9
 
11
10
  const getMirrorApiUrl = (): string => getEnv("API_HEDERA_MIRROR");
12
11
 
@@ -17,12 +16,12 @@ const fetch = (path: string) => {
17
16
  });
18
17
  };
19
18
 
20
- interface HederaMirrorAccount {
19
+ export interface Account {
21
20
  accountId: AccountId;
22
21
  balance: BigNumber;
23
22
  }
24
23
 
25
- export async function getAccountsForPublicKey(publicKey: string): Promise<HederaMirrorAccount[]> {
24
+ export async function getAccountsForPublicKey(publicKey: string): Promise<Account[]> {
26
25
  let r;
27
26
  try {
28
27
  r = await fetch(`/api/v1/accounts?account.publicKey=${publicKey}&balance=false`);
@@ -31,7 +30,7 @@ export async function getAccountsForPublicKey(publicKey: string): Promise<Hedera
31
30
  throw e;
32
31
  }
33
32
  const rawAccounts = r.data.accounts;
34
- const accounts: HederaMirrorAccount[] = [];
33
+ const accounts: Account[] = [];
35
34
 
36
35
  for (const raw of rawAccounts) {
37
36
  const accountBalance = await getAccountBalance(raw.account);
@@ -45,57 +44,37 @@ export async function getAccountsForPublicKey(publicKey: string): Promise<Hedera
45
44
  return accounts;
46
45
  }
47
46
 
48
- interface HederaMirrorTransfer {
49
- account: string;
50
- amount: number;
51
- }
52
-
53
47
  interface HederaMirrorTransaction {
54
48
  transfers: HederaMirrorTransfer[];
55
49
  charged_tx_fee: string;
56
50
  transaction_hash: string;
57
51
  consensus_timestamp: string;
58
- transaction_id: string;
59
52
  }
60
53
 
61
- export async function getAccountTransactions(
62
- address: string,
63
- since: string | null,
64
- ): Promise<HederaMirrorTransaction[]> {
65
- const transactions: HederaMirrorTransaction[] = [];
66
- const params = new URLSearchParams({
67
- "account.id": address,
68
- order: "desc",
69
- limit: "100",
70
- });
71
-
72
- if (since) {
73
- params.append("timestamp", `gt:${since}`);
74
- }
75
-
76
- let nextUrl = `/api/v1/transactions?${params.toString()}`;
77
-
78
- while (nextUrl) {
79
- const res = await fetch(nextUrl);
80
- const newTransactions = res.data.transactions as HederaMirrorTransaction[];
81
- if (newTransactions.length === 0) break;
82
- transactions.push(...newTransactions);
83
- nextUrl = res.data.links.next;
84
- }
85
-
86
- return transactions;
54
+ interface HederaMirrorTransfer {
55
+ account: string;
56
+ amount: number;
87
57
  }
88
58
 
89
59
  export async function getOperationsForAccount(
90
60
  ledgerAccountId: string,
91
61
  address: string,
92
- latestOperationTimestamp: string | null,
62
+ latestOperationTimestamp: string,
93
63
  ): Promise<Operation[]> {
94
- const rawOperations = await getAccountTransactions(address, latestOperationTimestamp);
95
64
  const operations: Operation[] = [];
65
+ let r = await fetch(
66
+ `/api/v1/transactions?account.id=${address}&timestamp=gt:${latestOperationTimestamp}`,
67
+ );
68
+ const rawOperations = r.data.transactions as HederaMirrorTransaction[];
69
+
70
+ while (r.data.links.next) {
71
+ r = await fetch(r.data.links.next);
72
+ const newOperations = r.data.transactions as HederaMirrorTransaction[];
73
+ rawOperations.push(...newOperations);
74
+ }
96
75
 
97
76
  for (const raw of rawOperations) {
98
- const { consensus_timestamp, transaction_id } = raw;
77
+ const { consensus_timestamp } = raw;
99
78
  const timestamp = new Date(parseInt(consensus_timestamp.split(".")[0], 10) * 1000);
100
79
  const senders: string[] = [];
101
80
  const recipients: string[] = [];
@@ -152,10 +131,7 @@ export async function getOperationsForAccount(
152
131
  // Set a value just so that it's considered confirmed according to isConfirmedOperation
153
132
  blockHeight: 5,
154
133
  blockHash: null,
155
- extra: {
156
- consensusTimestamp: consensus_timestamp,
157
- transactionId: transaction_id,
158
- } satisfies HederaOperationExtra,
134
+ extra: { consensusTimestamp: consensus_timestamp },
159
135
  fee,
160
136
  hash,
161
137
  recipients,
@@ -1,8 +1,8 @@
1
1
  import { Transaction as HederaSDKTransaction } from "@hashgraph/sdk";
2
- import { AccountBridge, Operation } from "@ledgerhq/types-live";
2
+ import { AccountBridge } from "@ledgerhq/types-live";
3
3
  import { patchOperationWithHash } from "@ledgerhq/coin-framework/operation";
4
- import { base64ToUrlSafeBase64, patchOperationWithExtra } from "./utils";
5
- import { HederaOperationExtra, Transaction } from "../types";
4
+ import { base64ToUrlSafeBase64 } from "./utils";
5
+ import { Transaction } from "../types";
6
6
  import { broadcastTransaction } from "../api/network";
7
7
 
8
8
  export const broadcast: AccountBridge<Transaction>["broadcast"] = async ({ signedOperation }) => {
@@ -15,13 +15,6 @@ export const broadcast: AccountBridge<Transaction>["broadcast"] = async ({ signe
15
15
 
16
16
  const base64Hash = Buffer.from(response.transactionHash).toString("base64");
17
17
  const base64HashUrlSafe = base64ToUrlSafeBase64(base64Hash);
18
- const extra: HederaOperationExtra = {
19
- transactionId: response.transactionId.toString(),
20
- };
21
18
 
22
- let patchedOperation: Operation = operation;
23
- patchedOperation = patchOperationWithHash(patchedOperation, base64HashUrlSafe);
24
- patchedOperation = patchOperationWithExtra(patchedOperation, extra);
25
-
26
- return patchedOperation;
19
+ return patchOperationWithHash(operation, base64HashUrlSafe);
27
20
  };
@@ -36,14 +36,14 @@ export const getAccountShape: GetAccountShape<Account> = async (
36
36
  // grab latest operation's consensus timestamp for incremental sync
37
37
  const oldOperations = initialAccount?.operations ?? [];
38
38
  const latestOperationTimestamp = oldOperations[0]
39
- ? new BigNumber(Math.floor(oldOperations[0].date.getTime() / 1000))
40
- : null;
39
+ ? Math.floor(oldOperations[0].date.getTime() / 1000)
40
+ : 0;
41
41
 
42
42
  // merge new operations w/ previously synced ones
43
43
  const newOperations = await getOperationsForAccount(
44
44
  liveAccountId,
45
45
  address,
46
- latestOperationTimestamp ? latestOperationTimestamp.toString() : null,
46
+ new BigNumber(latestOperationTimestamp).toString(),
47
47
  );
48
48
  const operations = mergeOps(oldOperations, newOperations);
49
49
 
@@ -1,9 +1,9 @@
1
1
  import BigNumber from "bignumber.js";
2
- import type { Account, Operation } from "@ledgerhq/types-live";
2
+ import type { Account } from "@ledgerhq/types-live";
3
3
  import cvsApi from "@ledgerhq/live-countervalues/api/index";
4
4
  import { getFiatCurrencyByTicker } from "@ledgerhq/cryptoassets";
5
5
  import { estimateMaxSpendable } from "./estimateMaxSpendable";
6
- import type { HederaOperationExtra, Transaction } from "../types";
6
+ import type { Transaction } from "../types";
7
7
 
8
8
  export const estimatedFeeSafetyRate = 2;
9
9
 
@@ -59,15 +59,3 @@ export function base64ToUrlSafeBase64(data: string): string {
59
59
 
60
60
  return data.replace(/\//g, "_").replace(/\+/g, "-");
61
61
  }
62
-
63
- export function patchOperationWithExtra(
64
- operation: Operation,
65
- extra: HederaOperationExtra,
66
- ): Operation {
67
- return {
68
- ...operation,
69
- extra,
70
- subOperations: (operation.subOperations ?? []).map(op => ({ ...op, extra })),
71
- nftOperations: (operation.nftOperations ?? []).map(op => ({ ...op, extra })),
72
- };
73
- }
@@ -29,5 +29,8 @@ export type TransactionStatusRaw = TransactionStatusCommonRaw;
29
29
 
30
30
  export type HederaOperationExtra = {
31
31
  consensusTimestamp?: string;
32
- transactionId?: string;
32
+ };
33
+
34
+ export type HederaOperationExtraRaw = {
35
+ consensusTimestamp?: string;
33
36
  };
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=mirror.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mirror.test.d.ts","sourceRoot":"","sources":["../../src/api/mirror.test.ts"],"names":[],"mappings":""}
@@ -1,60 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const network_1 = __importDefault(require("@ledgerhq/live-network/network"));
7
- const mirror_1 = require("./mirror");
8
- jest.mock("@ledgerhq/live-network/network");
9
- const mockedNetwork = jest.mocked(network_1.default);
10
- const makeMockResponse = (data) => ({
11
- data,
12
- status: 200,
13
- statusText: "OK",
14
- headers: {},
15
- config: {
16
- headers: {},
17
- },
18
- });
19
- describe("getAccountTransactions", () => {
20
- beforeEach(() => {
21
- jest.clearAllMocks();
22
- });
23
- it("should include 'account.id', 'limit=100' and 'order=desc' query params", async () => {
24
- mockedNetwork.mockResolvedValueOnce(makeMockResponse({ transactions: [], links: { next: null } }));
25
- await (0, mirror_1.getAccountTransactions)("0.0.1234", null);
26
- const calledUrl = mockedNetwork.mock.calls[0][0].url;
27
- expect(calledUrl).toContain("account.id=0.0.1234");
28
- expect(calledUrl).toContain("limit=100");
29
- expect(calledUrl).toContain("order=desc");
30
- });
31
- it("should break early if no transactions are returned", async () => {
32
- mockedNetwork.mockResolvedValueOnce(makeMockResponse({
33
- transactions: [],
34
- links: { next: "/next-1" },
35
- }));
36
- const result = await (0, mirror_1.getAccountTransactions)("0.0.1234", null);
37
- expect(mockedNetwork).toHaveBeenCalledTimes(1);
38
- expect(result).toEqual([]);
39
- });
40
- it("should keep fetching if links.next is present and new transactions are returned", async () => {
41
- mockedNetwork
42
- .mockResolvedValueOnce(makeMockResponse({
43
- transactions: [{ consensus_timestamp: "1" }],
44
- links: { next: "/next-1" },
45
- }))
46
- .mockResolvedValueOnce(makeMockResponse({
47
- transactions: [{ consensus_timestamp: "2" }],
48
- links: { next: "/next-2" },
49
- }))
50
- .mockResolvedValueOnce(makeMockResponse({
51
- transactions: [],
52
- links: { next: "/next-3" },
53
- }));
54
- const result = await (0, mirror_1.getAccountTransactions)("0.0.1234", null);
55
- expect(result).toHaveLength(2);
56
- expect(result.map(tx => tx.consensus_timestamp)).toEqual(["1", "2"]);
57
- expect(mockedNetwork).toHaveBeenCalledTimes(3);
58
- });
59
- });
60
- //# sourceMappingURL=mirror.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mirror.test.js","sourceRoot":"","sources":["../../src/api/mirror.test.ts"],"names":[],"mappings":"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"}
package/lib/logic.d.ts DELETED
@@ -1,5 +0,0 @@
1
- import { ExplorerView } from "@ledgerhq/types-cryptoassets";
2
- import { Operation } from "@ledgerhq/types-live";
3
- declare const getTransactionExplorer: (explorerView: ExplorerView | null | undefined, operation: Operation) => string | undefined;
4
- export { getTransactionExplorer };
5
- //# sourceMappingURL=logic.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"logic.d.ts","sourceRoot":"","sources":["../src/logic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAIjD,QAAA,MAAM,sBAAsB,iBACZ,YAAY,GAAG,IAAI,GAAG,SAAS,aAClC,SAAS,KACnB,MAAM,GAAG,SAIX,CAAC;AAEF,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
package/lib/logic.js DELETED
@@ -1,9 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getTransactionExplorer = void 0;
4
- const getTransactionExplorer = (explorerView, operation) => {
5
- const extra = operation.extra;
6
- return explorerView?.tx?.replace("$hash", extra.consensusTimestamp ?? extra.transactionId ?? "0");
7
- };
8
- exports.getTransactionExplorer = getTransactionExplorer;
9
- //# sourceMappingURL=logic.js.map
package/lib/logic.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"logic.js","sourceRoot":"","sources":["../src/logic.ts"],"names":[],"mappings":";;;AAKA,MAAM,sBAAsB,GAAG,CAC7B,YAA6C,EAC7C,SAAoB,EACA,EAAE;IACtB,MAAM,KAAK,GAAG,SAAS,CAAC,KAA6B,CAAC;IAEtD,OAAO,YAAY,EAAE,EAAE,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,kBAAkB,IAAI,KAAK,CAAC,aAAa,IAAI,GAAG,CAAC,CAAC;AACpG,CAAC,CAAC;AAEO,wDAAsB"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=logic.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"logic.test.d.ts","sourceRoot":"","sources":["../src/logic.test.ts"],"names":[],"mappings":""}
package/lib/logic.test.js DELETED
@@ -1,54 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const bignumber_js_1 = require("bignumber.js");
4
- const cryptoassets_1 = require("@ledgerhq/cryptoassets");
5
- const logic_1 = require("./logic");
6
- describe("getTransactionExplorer", () => {
7
- test("Tx explorer URL is converted from hash to consensus timestamp", async () => {
8
- const explorerView = (0, cryptoassets_1.getCryptoCurrencyById)("hedera").explorerViews[0];
9
- expect(explorerView).toBeDefined();
10
- expect(explorerView.tx).toBeDefined();
11
- const mockOperation = {
12
- extra: {
13
- consensusTimestamp: "1.2.3.4",
14
- },
15
- id: "",
16
- hash: "",
17
- type: "IN",
18
- value: new bignumber_js_1.BigNumber(0),
19
- fee: new bignumber_js_1.BigNumber(0),
20
- senders: [],
21
- recipients: [],
22
- blockHeight: undefined,
23
- blockHash: undefined,
24
- accountId: "",
25
- date: new Date(),
26
- };
27
- const newUrl = (0, logic_1.getTransactionExplorer)(explorerView, mockOperation);
28
- expect(newUrl).toBe("https://hashscan.io/mainnet/transaction/1.2.3.4");
29
- });
30
- test("Tx explorer URL is based on transaction id if consensus timestamp is not available", async () => {
31
- const explorerView = (0, cryptoassets_1.getCryptoCurrencyById)("hedera").explorerViews[0];
32
- expect(explorerView).toBeDefined();
33
- expect(explorerView.tx).toBeDefined();
34
- const mockOperation = {
35
- extra: {
36
- transactionId: "0.0.1234567-123-123",
37
- },
38
- id: "",
39
- hash: "",
40
- type: "IN",
41
- value: new bignumber_js_1.BigNumber(0),
42
- fee: new bignumber_js_1.BigNumber(0),
43
- senders: [],
44
- recipients: [],
45
- blockHeight: undefined,
46
- blockHash: undefined,
47
- accountId: "",
48
- date: new Date(),
49
- };
50
- const newUrl = (0, logic_1.getTransactionExplorer)(explorerView, mockOperation);
51
- expect(newUrl).toBe("https://hashscan.io/mainnet/transaction/0.0.1234567-123-123");
52
- });
53
- });
54
- //# sourceMappingURL=logic.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"logic.test.js","sourceRoot":"","sources":["../src/logic.test.ts"],"names":[],"mappings":";;AAAA,+CAAyC;AAEzC,yDAA+D;AAC/D,mCAAiD;AAEjD,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,IAAI,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,YAAY,GAAG,IAAA,oCAAqB,EAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;QACtE,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;QACnC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAEtC,MAAM,aAAa,GAAc;YAC/B,KAAK,EAAE;gBACL,kBAAkB,EAAE,SAAS;aAC9B;YACD,EAAE,EAAE,EAAE;YACN,IAAI,EAAE,EAAE;YACR,IAAI,EAAE,IAAI;YACV,KAAK,EAAE,IAAI,wBAAS,CAAC,CAAC,CAAC;YACvB,GAAG,EAAE,IAAI,wBAAS,CAAC,CAAC,CAAC;YACrB,OAAO,EAAE,EAAE;YACX,UAAU,EAAE,EAAE;YACd,WAAW,EAAE,SAAS;YACtB,SAAS,EAAE,SAAS;YACpB,SAAS,EAAE,EAAE;YACb,IAAI,EAAE,IAAI,IAAI,EAAE;SACjB,CAAC;QAEF,MAAM,MAAM,GAAG,IAAA,8BAAsB,EAAC,YAAY,EAAE,aAAa,CAAC,CAAC;QACnE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,oFAAoF,EAAE,KAAK,IAAI,EAAE;QACpG,MAAM,YAAY,GAAG,IAAA,oCAAqB,EAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;QACtE,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;QACnC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAEtC,MAAM,aAAa,GAAc;YAC/B,KAAK,EAAE;gBACL,aAAa,EAAE,qBAAqB;aACrC;YACD,EAAE,EAAE,EAAE;YACN,IAAI,EAAE,EAAE;YACR,IAAI,EAAE,IAAI;YACV,KAAK,EAAE,IAAI,wBAAS,CAAC,CAAC,CAAC;YACvB,GAAG,EAAE,IAAI,wBAAS,CAAC,CAAC,CAAC;YACrB,OAAO,EAAE,EAAE;YACX,UAAU,EAAE,EAAE;YACd,WAAW,EAAE,SAAS;YACtB,SAAS,EAAE,SAAS;YACpB,SAAS,EAAE,EAAE;YACb,IAAI,EAAE,IAAI,IAAI,EAAE;SACjB,CAAC;QAEF,MAAM,MAAM,GAAG,IAAA,8BAAsB,EAAC,YAAY,EAAE,aAAa,CAAC,CAAC;QACnE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=mirror.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mirror.test.d.ts","sourceRoot":"","sources":["../../src/api/mirror.test.ts"],"names":[],"mappings":""}
@@ -1,55 +0,0 @@
1
- import network from "@ledgerhq/live-network/network";
2
- import { getAccountTransactions } from "./mirror";
3
- jest.mock("@ledgerhq/live-network/network");
4
- const mockedNetwork = jest.mocked(network);
5
- const makeMockResponse = (data) => ({
6
- data,
7
- status: 200,
8
- statusText: "OK",
9
- headers: {},
10
- config: {
11
- headers: {},
12
- },
13
- });
14
- describe("getAccountTransactions", () => {
15
- beforeEach(() => {
16
- jest.clearAllMocks();
17
- });
18
- it("should include 'account.id', 'limit=100' and 'order=desc' query params", async () => {
19
- mockedNetwork.mockResolvedValueOnce(makeMockResponse({ transactions: [], links: { next: null } }));
20
- await getAccountTransactions("0.0.1234", null);
21
- const calledUrl = mockedNetwork.mock.calls[0][0].url;
22
- expect(calledUrl).toContain("account.id=0.0.1234");
23
- expect(calledUrl).toContain("limit=100");
24
- expect(calledUrl).toContain("order=desc");
25
- });
26
- it("should break early if no transactions are returned", async () => {
27
- mockedNetwork.mockResolvedValueOnce(makeMockResponse({
28
- transactions: [],
29
- links: { next: "/next-1" },
30
- }));
31
- const result = await getAccountTransactions("0.0.1234", null);
32
- expect(mockedNetwork).toHaveBeenCalledTimes(1);
33
- expect(result).toEqual([]);
34
- });
35
- it("should keep fetching if links.next is present and new transactions are returned", async () => {
36
- mockedNetwork
37
- .mockResolvedValueOnce(makeMockResponse({
38
- transactions: [{ consensus_timestamp: "1" }],
39
- links: { next: "/next-1" },
40
- }))
41
- .mockResolvedValueOnce(makeMockResponse({
42
- transactions: [{ consensus_timestamp: "2" }],
43
- links: { next: "/next-2" },
44
- }))
45
- .mockResolvedValueOnce(makeMockResponse({
46
- transactions: [],
47
- links: { next: "/next-3" },
48
- }));
49
- const result = await getAccountTransactions("0.0.1234", null);
50
- expect(result).toHaveLength(2);
51
- expect(result.map(tx => tx.consensus_timestamp)).toEqual(["1", "2"]);
52
- expect(mockedNetwork).toHaveBeenCalledTimes(3);
53
- });
54
- });
55
- //# sourceMappingURL=mirror.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mirror.test.js","sourceRoot":"","sources":["../../src/api/mirror.test.ts"],"names":[],"mappings":"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"}
package/lib-es/logic.d.ts DELETED
@@ -1,5 +0,0 @@
1
- import { ExplorerView } from "@ledgerhq/types-cryptoassets";
2
- import { Operation } from "@ledgerhq/types-live";
3
- declare const getTransactionExplorer: (explorerView: ExplorerView | null | undefined, operation: Operation) => string | undefined;
4
- export { getTransactionExplorer };
5
- //# sourceMappingURL=logic.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"logic.d.ts","sourceRoot":"","sources":["../src/logic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAIjD,QAAA,MAAM,sBAAsB,iBACZ,YAAY,GAAG,IAAI,GAAG,SAAS,aAClC,SAAS,KACnB,MAAM,GAAG,SAIX,CAAC;AAEF,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
package/lib-es/logic.js DELETED
@@ -1,6 +0,0 @@
1
- const getTransactionExplorer = (explorerView, operation) => {
2
- const extra = operation.extra;
3
- return explorerView?.tx?.replace("$hash", extra.consensusTimestamp ?? extra.transactionId ?? "0");
4
- };
5
- export { getTransactionExplorer };
6
- //# sourceMappingURL=logic.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"logic.js","sourceRoot":"","sources":["../src/logic.ts"],"names":[],"mappings":"AAKA,MAAM,sBAAsB,GAAG,CAC7B,YAA6C,EAC7C,SAAoB,EACA,EAAE;IACtB,MAAM,KAAK,GAAG,SAAS,CAAC,KAA6B,CAAC;IAEtD,OAAO,YAAY,EAAE,EAAE,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,kBAAkB,IAAI,KAAK,CAAC,aAAa,IAAI,GAAG,CAAC,CAAC;AACpG,CAAC,CAAC;AAEF,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=logic.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"logic.test.d.ts","sourceRoot":"","sources":["../src/logic.test.ts"],"names":[],"mappings":""}
@@ -1,52 +0,0 @@
1
- import { BigNumber } from "bignumber.js";
2
- import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets";
3
- import { getTransactionExplorer } from "./logic";
4
- describe("getTransactionExplorer", () => {
5
- test("Tx explorer URL is converted from hash to consensus timestamp", async () => {
6
- const explorerView = getCryptoCurrencyById("hedera").explorerViews[0];
7
- expect(explorerView).toBeDefined();
8
- expect(explorerView.tx).toBeDefined();
9
- const mockOperation = {
10
- extra: {
11
- consensusTimestamp: "1.2.3.4",
12
- },
13
- id: "",
14
- hash: "",
15
- type: "IN",
16
- value: new BigNumber(0),
17
- fee: new BigNumber(0),
18
- senders: [],
19
- recipients: [],
20
- blockHeight: undefined,
21
- blockHash: undefined,
22
- accountId: "",
23
- date: new Date(),
24
- };
25
- const newUrl = getTransactionExplorer(explorerView, mockOperation);
26
- expect(newUrl).toBe("https://hashscan.io/mainnet/transaction/1.2.3.4");
27
- });
28
- test("Tx explorer URL is based on transaction id if consensus timestamp is not available", async () => {
29
- const explorerView = getCryptoCurrencyById("hedera").explorerViews[0];
30
- expect(explorerView).toBeDefined();
31
- expect(explorerView.tx).toBeDefined();
32
- const mockOperation = {
33
- extra: {
34
- transactionId: "0.0.1234567-123-123",
35
- },
36
- id: "",
37
- hash: "",
38
- type: "IN",
39
- value: new BigNumber(0),
40
- fee: new BigNumber(0),
41
- senders: [],
42
- recipients: [],
43
- blockHeight: undefined,
44
- blockHash: undefined,
45
- accountId: "",
46
- date: new Date(),
47
- };
48
- const newUrl = getTransactionExplorer(explorerView, mockOperation);
49
- expect(newUrl).toBe("https://hashscan.io/mainnet/transaction/0.0.1234567-123-123");
50
- });
51
- });
52
- //# sourceMappingURL=logic.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"logic.test.js","sourceRoot":"","sources":["../src/logic.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAEjD,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,IAAI,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,YAAY,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;QACtE,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;QACnC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAEtC,MAAM,aAAa,GAAc;YAC/B,KAAK,EAAE;gBACL,kBAAkB,EAAE,SAAS;aAC9B;YACD,EAAE,EAAE,EAAE;YACN,IAAI,EAAE,EAAE;YACR,IAAI,EAAE,IAAI;YACV,KAAK,EAAE,IAAI,SAAS,CAAC,CAAC,CAAC;YACvB,GAAG,EAAE,IAAI,SAAS,CAAC,CAAC,CAAC;YACrB,OAAO,EAAE,EAAE;YACX,UAAU,EAAE,EAAE;YACd,WAAW,EAAE,SAAS;YACtB,SAAS,EAAE,SAAS;YACpB,SAAS,EAAE,EAAE;YACb,IAAI,EAAE,IAAI,IAAI,EAAE;SACjB,CAAC;QAEF,MAAM,MAAM,GAAG,sBAAsB,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;QACnE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,oFAAoF,EAAE,KAAK,IAAI,EAAE;QACpG,MAAM,YAAY,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;QACtE,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;QACnC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAEtC,MAAM,aAAa,GAAc;YAC/B,KAAK,EAAE;gBACL,aAAa,EAAE,qBAAqB;aACrC;YACD,EAAE,EAAE,EAAE;YACN,IAAI,EAAE,EAAE;YACR,IAAI,EAAE,IAAI;YACV,KAAK,EAAE,IAAI,SAAS,CAAC,CAAC,CAAC;YACvB,GAAG,EAAE,IAAI,SAAS,CAAC,CAAC,CAAC;YACrB,OAAO,EAAE,EAAE;YACX,UAAU,EAAE,EAAE;YACd,WAAW,EAAE,SAAS;YACtB,SAAS,EAAE,SAAS;YACpB,SAAS,EAAE,EAAE;YACb,IAAI,EAAE,IAAI,IAAI,EAAE;SACjB,CAAC;QAEF,MAAM,MAAM,GAAG,sBAAsB,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;QACnE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,76 +0,0 @@
1
- import network from "@ledgerhq/live-network/network";
2
- import { getAccountTransactions } from "./mirror";
3
-
4
- jest.mock("@ledgerhq/live-network/network");
5
- const mockedNetwork = jest.mocked(network);
6
-
7
- const makeMockResponse = (data: any): Awaited<ReturnType<typeof network>> => ({
8
- data,
9
- status: 200,
10
- statusText: "OK",
11
- headers: {},
12
- config: {
13
- headers: {} as any,
14
- },
15
- });
16
-
17
- describe("getAccountTransactions", () => {
18
- beforeEach(() => {
19
- jest.clearAllMocks();
20
- });
21
-
22
- it("should include 'account.id', 'limit=100' and 'order=desc' query params", async () => {
23
- mockedNetwork.mockResolvedValueOnce(
24
- makeMockResponse({ transactions: [], links: { next: null } }),
25
- );
26
-
27
- await getAccountTransactions("0.0.1234", null);
28
-
29
- const calledUrl = mockedNetwork.mock.calls[0][0].url;
30
- expect(calledUrl).toContain("account.id=0.0.1234");
31
- expect(calledUrl).toContain("limit=100");
32
- expect(calledUrl).toContain("order=desc");
33
- });
34
-
35
- it("should break early if no transactions are returned", async () => {
36
- mockedNetwork.mockResolvedValueOnce(
37
- makeMockResponse({
38
- transactions: [],
39
- links: { next: "/next-1" },
40
- }),
41
- );
42
-
43
- const result = await getAccountTransactions("0.0.1234", null);
44
-
45
- expect(mockedNetwork).toHaveBeenCalledTimes(1);
46
- expect(result).toEqual([]);
47
- });
48
-
49
- it("should keep fetching if links.next is present and new transactions are returned", async () => {
50
- mockedNetwork
51
- .mockResolvedValueOnce(
52
- makeMockResponse({
53
- transactions: [{ consensus_timestamp: "1" }],
54
- links: { next: "/next-1" },
55
- }),
56
- )
57
- .mockResolvedValueOnce(
58
- makeMockResponse({
59
- transactions: [{ consensus_timestamp: "2" }],
60
- links: { next: "/next-2" },
61
- }),
62
- )
63
- .mockResolvedValueOnce(
64
- makeMockResponse({
65
- transactions: [],
66
- links: { next: "/next-3" },
67
- }),
68
- );
69
-
70
- const result = await getAccountTransactions("0.0.1234", null);
71
-
72
- expect(result).toHaveLength(2);
73
- expect(result.map(tx => tx.consensus_timestamp)).toEqual(["1", "2"]);
74
- expect(mockedNetwork).toHaveBeenCalledTimes(3);
75
- });
76
- });