@ledgerhq/live-countervalues 0.1.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 (99) hide show
  1. package/.eslintrc.js +33 -0
  2. package/.turbo/turbo-build.log +4 -0
  3. package/.unimportedrc.json +4 -0
  4. package/CHANGELOG.md +1 -0
  5. package/LICENSE.txt +21 -0
  6. package/jest-global-setup.js +9 -0
  7. package/jest.config.js +14 -0
  8. package/lib/api/api.d.ts +4 -0
  9. package/lib/api/api.d.ts.map +1 -0
  10. package/lib/api/api.js +82 -0
  11. package/lib/api/api.js.map +1 -0
  12. package/lib/api/api.mock.d.ts +4 -0
  13. package/lib/api/api.mock.d.ts.map +1 -0
  14. package/lib/api/api.mock.js +103 -0
  15. package/lib/api/api.mock.js.map +1 -0
  16. package/lib/api/index.d.ts +4 -0
  17. package/lib/api/index.d.ts.map +1 -0
  18. package/lib/api/index.js +19 -0
  19. package/lib/api/index.js.map +1 -0
  20. package/lib/helpers.d.ts +31 -0
  21. package/lib/helpers.d.ts.map +1 -0
  22. package/lib/helpers.js +77 -0
  23. package/lib/helpers.js.map +1 -0
  24. package/lib/logic.d.ts +40 -0
  25. package/lib/logic.d.ts.map +1 -0
  26. package/lib/logic.integration.test.d.ts +2 -0
  27. package/lib/logic.integration.test.d.ts.map +1 -0
  28. package/lib/logic.integration.test.js +191 -0
  29. package/lib/logic.integration.test.js.map +1 -0
  30. package/lib/logic.js +422 -0
  31. package/lib/logic.js.map +1 -0
  32. package/lib/logic.test.d.ts +2 -0
  33. package/lib/logic.test.d.ts.map +1 -0
  34. package/lib/logic.test.js +29 -0
  35. package/lib/logic.test.js.map +1 -0
  36. package/lib/mock.d.ts +4 -0
  37. package/lib/mock.d.ts.map +1 -0
  38. package/lib/mock.js +10 -0
  39. package/lib/mock.js.map +1 -0
  40. package/lib/mock.test.d.ts +2 -0
  41. package/lib/mock.test.d.ts.map +1 -0
  42. package/lib/mock.test.js +210 -0
  43. package/lib/mock.test.js.map +1 -0
  44. package/lib/types.d.ts +47 -0
  45. package/lib/types.d.ts.map +1 -0
  46. package/lib/types.js +4 -0
  47. package/lib/types.js.map +1 -0
  48. package/lib-es/api/api.d.ts +4 -0
  49. package/lib-es/api/api.d.ts.map +1 -0
  50. package/lib-es/api/api.js +77 -0
  51. package/lib-es/api/api.js.map +1 -0
  52. package/lib-es/api/api.mock.d.ts +4 -0
  53. package/lib-es/api/api.mock.d.ts.map +1 -0
  54. package/lib-es/api/api.mock.js +98 -0
  55. package/lib-es/api/api.mock.js.map +1 -0
  56. package/lib-es/api/index.d.ts +4 -0
  57. package/lib-es/api/index.d.ts.map +1 -0
  58. package/lib-es/api/index.js +14 -0
  59. package/lib-es/api/index.js.map +1 -0
  60. package/lib-es/helpers.d.ts +31 -0
  61. package/lib-es/helpers.d.ts.map +1 -0
  62. package/lib-es/helpers.js +67 -0
  63. package/lib-es/helpers.js.map +1 -0
  64. package/lib-es/logic.d.ts +40 -0
  65. package/lib-es/logic.d.ts.map +1 -0
  66. package/lib-es/logic.integration.test.d.ts +2 -0
  67. package/lib-es/logic.integration.test.d.ts.map +1 -0
  68. package/lib-es/logic.integration.test.js +186 -0
  69. package/lib-es/logic.integration.test.js.map +1 -0
  70. package/lib-es/logic.js +406 -0
  71. package/lib-es/logic.js.map +1 -0
  72. package/lib-es/logic.test.d.ts +2 -0
  73. package/lib-es/logic.test.d.ts.map +1 -0
  74. package/lib-es/logic.test.js +27 -0
  75. package/lib-es/logic.test.js.map +1 -0
  76. package/lib-es/mock.d.ts +4 -0
  77. package/lib-es/mock.d.ts.map +1 -0
  78. package/lib-es/mock.js +6 -0
  79. package/lib-es/mock.js.map +1 -0
  80. package/lib-es/mock.test.d.ts +2 -0
  81. package/lib-es/mock.test.d.ts.map +1 -0
  82. package/lib-es/mock.test.js +205 -0
  83. package/lib-es/mock.test.js.map +1 -0
  84. package/lib-es/types.d.ts +47 -0
  85. package/lib-es/types.d.ts.map +1 -0
  86. package/lib-es/types.js +3 -0
  87. package/lib-es/types.js.map +1 -0
  88. package/package.json +81 -0
  89. package/src/api/api.mock.ts +117 -0
  90. package/src/api/api.ts +79 -0
  91. package/src/api/index.ts +19 -0
  92. package/src/helpers.ts +82 -0
  93. package/src/logic.integration.test.ts +209 -0
  94. package/src/logic.test.ts +30 -0
  95. package/src/logic.ts +533 -0
  96. package/src/mock.test.ts +231 -0
  97. package/src/mock.ts +8 -0
  98. package/src/types.ts +70 -0
  99. package/tsconfig.json +15 -0
@@ -0,0 +1,117 @@
1
+ import type { CounterValuesAPI, RateGranularity } from "../types";
2
+ import { getEnv } from "@ledgerhq/live-env";
3
+ import { getBTCValues, BTCtoUSD, referenceSnapshotDate } from "../mock";
4
+ import { formatPerGranularity } from "../helpers";
5
+ import Prando from "prando";
6
+ import { findCurrencyByTicker } from "@ledgerhq/coin-framework/currencies/index";
7
+
8
+ const DAY = 24 * 60 * 60 * 1000;
9
+
10
+ function btcTrend(t: number) {
11
+ const daysSinceGenesis = (t - 1230937200000) / DAY;
12
+ return Math.pow(daysSinceGenesis / 693, 5.526);
13
+ }
14
+
15
+ const randomCache: Record<string, number> = {};
16
+
17
+ function fromToRandom(id: string) {
18
+ if (randomCache[id]) return randomCache[id];
19
+ return (randomCache[id] = new Prando(getEnv("MOCK") + id).next());
20
+ }
21
+
22
+ function temporalFactor(from: string, to: string, maybeDate: Date | undefined) {
23
+ const t = (maybeDate || new Date()).getTime();
24
+ const r = fromToRandom(from); // make it varies between rates...
25
+
26
+ const wave1 = Math.cos(r * 0.5 + t / (200 * DAY * (0.5 + 0.5 * r)));
27
+ // long term wave
28
+ const wave2 = Math.sin(r + t / (30 * DAY)); // short term wave
29
+
30
+ const wave3 = // random market perturbation
31
+ Math.max(0, Math.sin(t / (66 * DAY))) *
32
+ Math.cos(wave2 + Math.cos(r) + t / (3 * DAY * (1 - 0.1 * r)));
33
+
34
+ // This is essentially randomness!
35
+ if (maybeDate && Math.cos(7 * r + t * 0.1) > 0.9 + 0.1 * r) {
36
+ return 0; // intentionally set a GAP into the data
37
+ }
38
+
39
+ const res =
40
+ (0.2 - 0.2 * r * r) * wave1 +
41
+ (0.1 + 0.05 * Math.sin(r)) * wave2 +
42
+ 0.05 * wave3 +
43
+ btcTrend(t) / btcTrend(referenceSnapshotDate.getTime());
44
+ return Math.max(0, res);
45
+ }
46
+
47
+ function rate(from: string, to: string, date?: Date): number | undefined {
48
+ const asBTC = getBTCValues()[from];
49
+ if (!asBTC) return;
50
+
51
+ if (to === "BTC") {
52
+ return asBTC * temporalFactor(from, to, date);
53
+ }
54
+
55
+ if (to === "USD") {
56
+ return asBTC * BTCtoUSD * temporalFactor(from, to, date);
57
+ }
58
+
59
+ if (from === "BTC") {
60
+ const r = rate(to, from, date);
61
+ if (!r) return;
62
+ return 1 / r;
63
+ }
64
+
65
+ const btcTO = rate("BTC", to, date);
66
+
67
+ if (btcTO) {
68
+ return asBTC * btcTO * temporalFactor(from, to, date);
69
+ }
70
+ }
71
+
72
+ const increment = {
73
+ daily: DAY,
74
+ hourly: 60 * 60 * 1000,
75
+ };
76
+
77
+ function getIds(): string[] {
78
+ const ids: string[] = [];
79
+ for (const k in getBTCValues()) {
80
+ const c = findCurrencyByTicker(k);
81
+ if (c && (c.type == "CryptoCurrency" || c.type == "TokenCurrency")) {
82
+ ids.push(c.id);
83
+ }
84
+ }
85
+ return ids;
86
+ }
87
+
88
+ function getDates(granularity: RateGranularity, start: Date): Date[] {
89
+ const array: Date[] = [];
90
+ const f = formatPerGranularity[granularity];
91
+ const incr = increment[granularity];
92
+ const initial = new Date(f(start || new Date())).getTime();
93
+ const now = Date.now();
94
+
95
+ for (let t = initial; t < now; t += incr) {
96
+ array.push(new Date(t));
97
+ }
98
+
99
+ return array;
100
+ }
101
+
102
+ const api: CounterValuesAPI = {
103
+ fetchHistorical: (granularity, { from, to, startDate }) => {
104
+ const r: Record<string, number> = {};
105
+ const f = formatPerGranularity[granularity];
106
+ getDates(granularity, startDate).forEach(date => {
107
+ const v = rate(from.ticker, to.ticker, date);
108
+ if (v) {
109
+ r[f(date)] = v;
110
+ }
111
+ });
112
+ return Promise.resolve(r);
113
+ },
114
+ fetchLatest: pairs => Promise.resolve(pairs.map(({ from, to }) => rate(from.ticker, to.ticker))),
115
+ fetchIdsSortedByMarketcap: () => Promise.resolve(getIds()),
116
+ };
117
+ export default api;
package/src/api/api.ts ADDED
@@ -0,0 +1,79 @@
1
+ import network from "@ledgerhq/live-network/network";
2
+ import URL from "url";
3
+ import { getEnv } from "@ledgerhq/live-env";
4
+ import { promiseAllBatched } from "@ledgerhq/live-promise";
5
+ import { formatPerGranularity, inferCurrencyAPIID } from "../helpers";
6
+ import type { CounterValuesAPI, TrackingPair } from "../types";
7
+ import type { Currency } from "@ledgerhq/types-cryptoassets";
8
+
9
+ const baseURL = () => getEnv("LEDGER_COUNTERVALUES_API");
10
+
11
+ const LATEST_CHUNK = 50;
12
+
13
+ const api: CounterValuesAPI = {
14
+ fetchHistorical: async (granularity, { from, to, startDate }) => {
15
+ const format = formatPerGranularity[granularity];
16
+ const url = URL.format({
17
+ pathname: `${baseURL()}/v3/historical/${granularity}/simple`,
18
+ query: {
19
+ from: inferCurrencyAPIID(from),
20
+ to: inferCurrencyAPIID(to),
21
+ start: format(startDate),
22
+ end: format(new Date()),
23
+ },
24
+ });
25
+ const { data } = await network({ method: "GET", url });
26
+ return data;
27
+ },
28
+
29
+ fetchLatest: async (pairs: TrackingPair[]): Promise<number[]> => {
30
+ if (pairs.length === 0) return [];
31
+
32
+ // we group the pairs into chunks (of max LATEST_CHUNK) of the same "to" currency
33
+ // the batches preserve the ordering of "pairs" so the output returns the result in same orders
34
+ // we essentially assume that pairs' to's field are not changing / are sorted
35
+ const batches: Array<[Currency[], Currency]> = []; // array of [froms, to]
36
+ const first = pairs[0];
37
+ let batch: [Currency[], Currency] = [[first.from], first.to];
38
+ for (let i = 1; i < pairs.length; i++) {
39
+ const pair = pairs[i];
40
+ if (pair.to !== batch[1] || batch[0].length >= LATEST_CHUNK) {
41
+ batches.push(batch);
42
+ batch = [[pair.from], pair.to];
43
+ } else {
44
+ batch[0].push(pair.from);
45
+ }
46
+ }
47
+ batches.push(batch);
48
+
49
+ const all = await promiseAllBatched(4, batches, async ([froms, to]): Promise<number[]> => {
50
+ const fromIds = froms.map(inferCurrencyAPIID);
51
+ const url = URL.format({
52
+ pathname: `${baseURL()}/v3/spot/simple`,
53
+ query: {
54
+ to: inferCurrencyAPIID(to),
55
+ froms: fromIds.join(","),
56
+ },
57
+ });
58
+
59
+ const { data } = await network({ method: "GET", url });
60
+
61
+ // backend returns an object with keys being the froms
62
+ return fromIds.map(id => data[id] || 0);
63
+ });
64
+
65
+ const data = all.flat();
66
+
67
+ return data;
68
+ },
69
+
70
+ fetchIdsSortedByMarketcap: async () => {
71
+ const { data } = await network({
72
+ method: "GET",
73
+ url: `${baseURL()}/v3/currencies/supported`,
74
+ });
75
+ return data;
76
+ },
77
+ };
78
+
79
+ export default api;
@@ -0,0 +1,19 @@
1
+ import { getEnv } from "@ledgerhq/live-env";
2
+ import type { CounterValuesAPI } from "../types";
3
+ import prodAPI from "./api";
4
+ import mockAPI from "./api.mock";
5
+
6
+ const api: CounterValuesAPI = {
7
+ fetchHistorical: (granularity, pair) =>
8
+ getEnv("MOCK_COUNTERVALUES")
9
+ ? mockAPI.fetchHistorical(granularity, pair)
10
+ : prodAPI.fetchHistorical(granularity, pair),
11
+ fetchLatest: pairs =>
12
+ getEnv("MOCK_COUNTERVALUES") ? mockAPI.fetchLatest(pairs) : prodAPI.fetchLatest(pairs),
13
+ fetchIdsSortedByMarketcap: () =>
14
+ getEnv("MOCK_COUNTERVALUES")
15
+ ? mockAPI.fetchIdsSortedByMarketcap()
16
+ : prodAPI.fetchIdsSortedByMarketcap(),
17
+ };
18
+
19
+ export default api;
package/src/helpers.ts ADDED
@@ -0,0 +1,82 @@
1
+ import { Currency } from "@ledgerhq/types-cryptoassets";
2
+ import type { RateGranularity } from "./types";
3
+
4
+ export const inferCurrencyAPIID = (currency: Currency): string => {
5
+ switch (currency.type) {
6
+ case "FiatCurrency": {
7
+ return currency.ticker;
8
+ }
9
+ case "CryptoCurrency":
10
+ case "TokenCurrency": {
11
+ return currency.id;
12
+ }
13
+ }
14
+ };
15
+
16
+ const HOUR = 60 * 60 * 1000;
17
+ const DAY = 24 * HOUR;
18
+
19
+ export const incrementPerGranularity: Record<RateGranularity, number> = {
20
+ daily: DAY,
21
+ hourly: HOUR,
22
+ };
23
+
24
+ export const datapointLimits: Record<RateGranularity, number> = {
25
+ daily: 9999 * DAY,
26
+ hourly: 7 * DAY, // we fetch at MOST a week of hourly. after that there are too much data...
27
+ };
28
+
29
+ /**
30
+ * efficient implementation of YYYY-MM-DD formatter
31
+ * @memberof countervalue
32
+ */
33
+ export const formatCounterValueDay = (d: Date): string => d.toISOString().slice(0, 10);
34
+
35
+ /**
36
+ * efficient implementation of YYYY-MM-DDTHH formatter
37
+ * @memberof countervalue
38
+ */
39
+ export const formatCounterValueHour = (d: Date): string => d.toISOString().slice(0, 13);
40
+
41
+ /**
42
+ * full version of formatCounterValue*
43
+ */
44
+ export const formatCounterValueHashes = (d: Date): { iso: string; day: string; hour: string } => {
45
+ const iso = d.toISOString();
46
+ return {
47
+ iso,
48
+ day: iso.slice(0, 10),
49
+ hour: iso.slice(0, 13),
50
+ };
51
+ };
52
+
53
+ export const parseFormattedDate = (str: string): Date => {
54
+ let full = str;
55
+
56
+ switch (str.length) {
57
+ case 10:
58
+ full += "T00:00";
59
+ break;
60
+
61
+ case 13:
62
+ full += ":00";
63
+ break;
64
+ }
65
+
66
+ full += ":00.000Z";
67
+ return new Date(full);
68
+ };
69
+
70
+ export const formatPerGranularity: Record<RateGranularity, (arg0: Date) => string> = {
71
+ daily: formatCounterValueDay,
72
+ hourly: formatCounterValueHour,
73
+ };
74
+
75
+ // a hash function used to identify a pair of currencies, internal use only (not used for the API)
76
+ export function pairId({ from, to }: { from: Currency; to: Currency }): string {
77
+ return `${inferCurrencyAPIID(to)} ${inferCurrencyAPIID(from)}`;
78
+ }
79
+
80
+ export function magFromTo(from: Currency, to: Currency): number {
81
+ return 10 ** (to.units[0].magnitude - from.units[0].magnitude);
82
+ }
@@ -0,0 +1,209 @@
1
+ import {
2
+ initialState,
3
+ loadCountervalues,
4
+ calculate,
5
+ exportCountervalues,
6
+ importCountervalues,
7
+ } from "./logic";
8
+ import { findCurrencyByTicker } from "@ledgerhq/coin-framework/currencies/index";
9
+ import {
10
+ getFiatCurrencyByTicker,
11
+ getCryptoCurrencyById,
12
+ getTokenById,
13
+ } from "@ledgerhq/cryptoassets";
14
+ import { getBTCValues } from "./mock";
15
+ import { Currency } from "@ledgerhq/types-cryptoassets";
16
+ import Prando from "prando";
17
+ import api from "./api";
18
+ import { setEnv } from "@ledgerhq/live-env";
19
+
20
+ const value = "ll-ci/0.0.0";
21
+ setEnv("LEDGER_CLIENT_VERSION", value);
22
+
23
+ const ethereum = getCryptoCurrencyById("ethereum");
24
+ const bitcoin = getCryptoCurrencyById("bitcoin");
25
+ const usd = getFiatCurrencyByTicker("USD");
26
+ const now = Date.now();
27
+
28
+ const DAY = 24 * 60 * 60 * 1000;
29
+
30
+ jest.setTimeout(60000);
31
+
32
+ describe("API sanity", () => {
33
+ test("recent days have rate for BTC USD", async () => {
34
+ const state = await loadCountervalues(initialState, {
35
+ trackingPairs: [
36
+ {
37
+ from: bitcoin,
38
+ to: usd,
39
+ startDate: new Date(now - 200 * DAY),
40
+ },
41
+ ],
42
+ autofillGaps: false,
43
+ disableAutoRecoverErrors: true,
44
+ });
45
+
46
+ for (let i = 0; i < 7; i++) {
47
+ const value = calculate(state, {
48
+ date: new Date(now - i * DAY),
49
+ disableRounding: true,
50
+ from: bitcoin,
51
+ to: usd,
52
+ value: 1000000,
53
+ });
54
+ expect(value).toBeDefined();
55
+ }
56
+ });
57
+ test("recent days have different rates for BTC USD", async () => {
58
+ const state = await loadCountervalues(initialState, {
59
+ trackingPairs: [
60
+ {
61
+ from: bitcoin,
62
+ to: usd,
63
+ startDate: new Date(now - 200 * DAY),
64
+ },
65
+ ],
66
+ autofillGaps: true,
67
+ disableAutoRecoverErrors: true,
68
+ });
69
+ const currentValue = calculate(state, {
70
+ disableRounding: true,
71
+ from: bitcoin,
72
+ to: usd,
73
+ value: 1000000,
74
+ });
75
+
76
+ for (let i = 1; i < 7; i++) {
77
+ const value = calculate(state, {
78
+ date: new Date(now - i * DAY),
79
+ disableRounding: true,
80
+ from: bitcoin,
81
+ to: usd,
82
+ value: 1000000,
83
+ });
84
+ expect(value).not.toEqual(currentValue);
85
+ }
86
+ });
87
+ });
88
+
89
+ describe("extreme cases", () => {
90
+ const universe = Object.keys(getBTCValues())
91
+ .filter(t => t !== "USD")
92
+ .map(findCurrencyByTicker)
93
+ .filter(Boolean) as Currency[];
94
+ universe.sort((a, b) => a.ticker.localeCompare(b.ticker));
95
+ const prando = new Prando("orderingrng");
96
+ const sampleCount = Math.min(120, universe.length);
97
+ const i = prando.nextInt(0, universe.length - sampleCount);
98
+ const currencies = universe.slice(i, i + sampleCount);
99
+
100
+ test("all tickers against USD", async () => {
101
+ const state = await loadCountervalues(initialState, {
102
+ trackingPairs: currencies.map(from => ({
103
+ from,
104
+ to: usd,
105
+ startDate: new Date(),
106
+ })),
107
+ autofillGaps: true,
108
+ disableAutoRecoverErrors: true,
109
+ });
110
+
111
+ const currenciesWithCVs = currencies
112
+ .map(from =>
113
+ calculate(state, {
114
+ date: new Date(),
115
+ from,
116
+ to: usd,
117
+ value: 1000000,
118
+ }),
119
+ )
120
+ .filter(v => v && v > 0);
121
+
122
+ expect(currenciesWithCVs.length).toBeGreaterThan(0);
123
+ });
124
+
125
+ test("all tickers against BTC", async () => {
126
+ const state = await loadCountervalues(initialState, {
127
+ trackingPairs: currencies.map(from => ({
128
+ from,
129
+ to: bitcoin,
130
+ startDate: new Date(),
131
+ })),
132
+ autofillGaps: true,
133
+ disableAutoRecoverErrors: true,
134
+ });
135
+
136
+ const currenciesWithCVs = currencies
137
+ .map(from =>
138
+ calculate(state, {
139
+ date: new Date(),
140
+ from,
141
+ to: bitcoin,
142
+ value: 1000000,
143
+ }),
144
+ )
145
+ .filter(v => v && v > 0);
146
+
147
+ expect(currenciesWithCVs.length).toBeGreaterThan(0);
148
+ });
149
+ });
150
+
151
+ describe("WETH rules", () => {
152
+ // this test is created to confirm the recent removal of weth/eth specific management is still functional in v3 context
153
+ test("ethereum WETH have countervalues", async () => {
154
+ const weth = getTokenById("ethereum/erc20/weth");
155
+ const state = await loadCountervalues(initialState, {
156
+ trackingPairs: [
157
+ {
158
+ from: weth,
159
+ to: usd,
160
+ startDate: new Date(now - 10 * DAY),
161
+ },
162
+ ],
163
+ autofillGaps: true,
164
+ disableAutoRecoverErrors: true,
165
+ });
166
+ const value = calculate(state, {
167
+ disableRounding: true,
168
+ from: weth,
169
+ to: usd,
170
+ value: 1000000,
171
+ });
172
+ expect(value).toBeGreaterThan(0);
173
+ });
174
+ });
175
+
176
+ test("fetchIdsSortedByMarketcap", async () => {
177
+ const ids = await api.fetchIdsSortedByMarketcap();
178
+ expect(ids).toContain("bitcoin");
179
+ });
180
+
181
+ test("export and import it back", async () => {
182
+ const settings = {
183
+ trackingPairs: [
184
+ {
185
+ from: bitcoin,
186
+ to: usd,
187
+ startDate: new Date(now - 10 * DAY),
188
+ },
189
+ {
190
+ from: ethereum,
191
+ to: usd,
192
+ startDate: new Date(now - 100 * DAY),
193
+ },
194
+ ],
195
+ autofillGaps: true,
196
+ disableAutoRecoverErrors: true,
197
+ };
198
+ const state = await loadCountervalues(initialState, settings);
199
+ const exported = exportCountervalues(state);
200
+ const imported = importCountervalues(exported, settings);
201
+ expect(imported).toEqual(state);
202
+ });
203
+
204
+ describe("API specific unit tests", () => {
205
+ test("fetchLatest with empty pairs", async () => {
206
+ const rates = await api.fetchLatest([]);
207
+ expect(rates).toEqual([]);
208
+ });
209
+ });
@@ -0,0 +1,30 @@
1
+ import { genAccount } from "@ledgerhq/coin-framework/mocks/account";
2
+ import { getCryptoCurrencyById, getFiatCurrencyByTicker } from "@ledgerhq/cryptoassets";
3
+ import { inferTrackingPairForAccounts } from "./logic";
4
+
5
+ describe("inferTrackingPairForAccounts", () => {
6
+ const accounts = Array(20)
7
+ .fill(null)
8
+ .map((_, i) => genAccount("test" + i));
9
+ const usd = getFiatCurrencyByTicker("USD");
10
+
11
+ test("trackingPairs have a deterministic order regardless of accounts order", () => {
12
+ const trackingPairs = inferTrackingPairForAccounts(accounts, usd);
13
+ const accounts2 = accounts.slice(10).concat(accounts.slice(0, 10));
14
+ const trackingPairs2 = inferTrackingPairForAccounts(accounts2, usd);
15
+ expect(trackingPairs).toEqual(trackingPairs2);
16
+ });
17
+
18
+ test("trackingPairs with same from and to are filtered out", () => {
19
+ const first = genAccount("test1", { currency: getCryptoCurrencyById("bitcoin") });
20
+ const trackingPairs = inferTrackingPairForAccounts([first], first.currency);
21
+ expect(trackingPairs).toEqual([]);
22
+ });
23
+
24
+ test("trackingPairs with 2 accounts of same coin yield one tracking pair", () => {
25
+ const first = genAccount("test1", { currency: getCryptoCurrencyById("bitcoin") });
26
+ const second = genAccount("test2", { currency: getCryptoCurrencyById("bitcoin") });
27
+ const trackingPairs = inferTrackingPairForAccounts([first, second], usd);
28
+ expect(trackingPairs.length).toBe(1);
29
+ });
30
+ });