@ledgerhq/live-common 21.25.1 → 21.27.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 (211) hide show
  1. package/lib/__tests__/currencies.js +1 -1
  2. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.synced.unit.test.js +27 -26
  3. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.synced.unit.test.js.map +1 -1
  4. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.syncing.integration.test.js +9 -7
  5. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.syncing.integration.test.js.map +1 -1
  6. package/lib/account/support.d.ts.map +1 -1
  7. package/lib/account/support.js +1 -25
  8. package/lib/account/support.js.map +1 -1
  9. package/lib/api/Ethereum.d.ts.map +1 -1
  10. package/lib/api/Ethereum.js +1 -4
  11. package/lib/api/Ethereum.js.map +1 -1
  12. package/lib/api/explorerConfig/index.d.ts.map +1 -1
  13. package/lib/api/explorerConfig/index.js +0 -4
  14. package/lib/api/explorerConfig/index.js.map +1 -1
  15. package/lib/bridge/jsHelpers.js +1 -1
  16. package/lib/bridge/jsHelpers.js.map +1 -1
  17. package/lib/data/icons/react/index.d.ts +1 -0
  18. package/lib/data/icons/react/index.d.ts.map +1 -1
  19. package/lib/data/icons/react/index.js +4 -2
  20. package/lib/data/icons/react/index.js.map +1 -1
  21. package/lib/data/icons/react/sol.js +3 -3
  22. package/lib/data/icons/react/sol.js.map +1 -1
  23. package/lib/data/icons/react/tyc.d.ts +8 -0
  24. package/lib/data/icons/react/tyc.d.ts.map +1 -0
  25. package/lib/data/icons/react/tyc.js +33 -0
  26. package/lib/data/icons/react/tyc.js.map +1 -0
  27. package/lib/data/icons/reactNative/index.d.ts +1 -0
  28. package/lib/data/icons/reactNative/index.d.ts.map +1 -1
  29. package/lib/data/icons/reactNative/index.js +4 -2
  30. package/lib/data/icons/reactNative/index.js.map +1 -1
  31. package/lib/data/icons/reactNative/sol.js +3 -3
  32. package/lib/data/icons/reactNative/sol.js.map +1 -1
  33. package/lib/data/icons/reactNative/tyc.d.ts +8 -0
  34. package/lib/data/icons/reactNative/tyc.d.ts.map +1 -0
  35. package/lib/data/icons/reactNative/tyc.js +34 -0
  36. package/lib/data/icons/reactNative/tyc.js.map +1 -0
  37. package/lib/env.d.ts +19 -5
  38. package/lib/env.d.ts.map +1 -1
  39. package/lib/env.js +14 -0
  40. package/lib/env.js.map +1 -1
  41. package/lib/env.react.d.ts +1 -1
  42. package/lib/env.react.d.ts.map +1 -1
  43. package/lib/errors.d.ts +3 -0
  44. package/lib/errors.d.ts.map +1 -1
  45. package/lib/errors.js +3 -2
  46. package/lib/errors.js.map +1 -1
  47. package/lib/exchange/hw-app-exchange/Exchange.d.ts +12 -15
  48. package/lib/exchange/hw-app-exchange/Exchange.d.ts.map +1 -1
  49. package/lib/exchange/hw-app-exchange/Exchange.js +4 -13
  50. package/lib/exchange/hw-app-exchange/Exchange.js.map +1 -1
  51. package/lib/exchange/index.d.ts.map +1 -1
  52. package/lib/exchange/index.js +9 -2
  53. package/lib/exchange/index.js.map +1 -1
  54. package/lib/exchange/platform/completeExchange.d.ts +6 -0
  55. package/lib/exchange/platform/completeExchange.d.ts.map +1 -0
  56. package/lib/exchange/platform/completeExchange.js +24 -0
  57. package/lib/exchange/platform/completeExchange.js.map +1 -0
  58. package/lib/exchange/platform/serialization.d.ts +4 -0
  59. package/lib/exchange/platform/serialization.d.ts.map +1 -0
  60. package/lib/exchange/platform/serialization.js +57 -0
  61. package/lib/exchange/platform/serialization.js.map +1 -0
  62. package/lib/exchange/{swap → platform}/startExchange.d.ts +0 -0
  63. package/lib/exchange/platform/startExchange.d.ts.map +1 -0
  64. package/lib/exchange/{swap → platform}/startExchange.js +3 -3
  65. package/lib/exchange/platform/startExchange.js.map +1 -0
  66. package/lib/exchange/platform/transfer/completeExchange.d.ts +5 -0
  67. package/lib/exchange/platform/transfer/completeExchange.d.ts.map +1 -0
  68. package/lib/exchange/platform/transfer/completeExchange.js +198 -0
  69. package/lib/exchange/platform/transfer/completeExchange.js.map +1 -0
  70. package/lib/exchange/platform/transfer/index.d.ts +5 -0
  71. package/lib/exchange/platform/transfer/index.d.ts.map +1 -0
  72. package/lib/exchange/platform/transfer/index.js +49 -0
  73. package/lib/exchange/platform/transfer/index.js.map +1 -0
  74. package/lib/exchange/platform/types.d.ts +59 -0
  75. package/lib/exchange/platform/types.d.ts.map +1 -0
  76. package/lib/exchange/platform/types.js +3 -0
  77. package/lib/exchange/platform/types.js.map +1 -0
  78. package/lib/exchange/sell/checkSignatureAndPrepare.js +1 -2
  79. package/lib/exchange/sell/checkSignatureAndPrepare.js.map +1 -1
  80. package/lib/exchange/sell/getTransactionId.js +1 -2
  81. package/lib/exchange/sell/getTransactionId.js.map +1 -1
  82. package/lib/exchange/sell/index.d.ts +6 -0
  83. package/lib/exchange/sell/index.d.ts.map +1 -1
  84. package/lib/exchange/sell/index.js +3 -3
  85. package/lib/exchange/sell/index.js.map +1 -1
  86. package/lib/exchange/swap/completeExchange.d.ts +2 -13
  87. package/lib/exchange/swap/completeExchange.d.ts.map +1 -1
  88. package/lib/exchange/swap/completeExchange.js +2 -4
  89. package/lib/exchange/swap/completeExchange.js.map +1 -1
  90. package/lib/exchange/swap/initSwap.d.ts.map +1 -1
  91. package/lib/exchange/swap/initSwap.js +3 -4
  92. package/lib/exchange/swap/initSwap.js.map +1 -1
  93. package/lib/exchange/swap/types.d.ts +0 -12
  94. package/lib/exchange/swap/types.d.ts.map +1 -1
  95. package/lib/exchange/testCurrencyConfig/data/coins.d.ts +8 -0
  96. package/lib/exchange/testCurrencyConfig/data/coins.d.ts.map +1 -0
  97. package/lib/exchange/testCurrencyConfig/data/coins.js +21 -0
  98. package/lib/exchange/testCurrencyConfig/data/coins.js.map +1 -0
  99. package/lib/exchange/testCurrencyConfig/index.d.ts +8 -0
  100. package/lib/exchange/testCurrencyConfig/index.d.ts.map +1 -0
  101. package/lib/exchange/testCurrencyConfig/index.js +68 -0
  102. package/lib/exchange/testCurrencyConfig/index.js.map +1 -0
  103. package/lib/families/bitcoin/test-dataset.js +1 -1
  104. package/lib/families/bitcoin/test-dataset.js.map +1 -1
  105. package/lib/families/bitcoin/wallet-btc/wallet.d.ts.map +1 -1
  106. package/lib/families/bitcoin/wallet-btc/wallet.js +10 -2
  107. package/lib/families/bitcoin/wallet-btc/wallet.js.map +1 -1
  108. package/lib/families/ethereum/modules/send.d.ts.map +1 -1
  109. package/lib/families/ethereum/modules/send.js +3 -1
  110. package/lib/families/ethereum/modules/send.js.map +1 -1
  111. package/lib/families/ethereum/specs.d.ts.map +1 -1
  112. package/lib/families/ethereum/specs.js +2 -0
  113. package/lib/families/ethereum/specs.js.map +1 -1
  114. package/lib/families/tezos/bridge/js.d.ts.map +1 -1
  115. package/lib/families/tezos/bridge/js.js +19 -20
  116. package/lib/families/tezos/bridge/js.js.map +1 -1
  117. package/lib/families/tezos/hw-getAddress.d.ts.map +1 -1
  118. package/lib/families/tezos/hw-getAddress.js +1 -3
  119. package/lib/families/tezos/hw-getAddress.js.map +1 -1
  120. package/lib/families/tezos/synchronisation.d.ts.map +1 -1
  121. package/lib/families/tezos/synchronisation.js +1 -1
  122. package/lib/families/tezos/synchronisation.js.map +1 -1
  123. package/lib/hw/actions/app.d.ts +3 -0
  124. package/lib/hw/actions/app.d.ts.map +1 -1
  125. package/lib/hw/actions/app.js +7 -1
  126. package/lib/hw/actions/app.js.map +1 -1
  127. package/lib/hw/actions/completeExchange.d.ts +1 -0
  128. package/lib/hw/actions/completeExchange.d.ts.map +1 -1
  129. package/lib/hw/actions/completeExchange.js +4 -3
  130. package/lib/hw/actions/completeExchange.js.map +1 -1
  131. package/lib/hw/connectApp.d.ts +5 -0
  132. package/lib/hw/connectApp.d.ts.map +1 -1
  133. package/lib/hw/connectApp.js +11 -1
  134. package/lib/hw/connectApp.js.map +1 -1
  135. package/lib/market/MarketDataProvider.d.ts +26 -0
  136. package/lib/market/MarketDataProvider.d.ts.map +1 -0
  137. package/lib/market/MarketDataProvider.js +339 -0
  138. package/lib/market/MarketDataProvider.js.map +1 -0
  139. package/lib/market/api/api.d.ts +15 -0
  140. package/lib/market/api/api.d.ts.map +1 -0
  141. package/lib/market/api/api.js +261 -0
  142. package/lib/market/api/api.js.map +1 -0
  143. package/lib/market/api/api.mock.d.ts +15 -0
  144. package/lib/market/api/api.mock.d.ts.map +1 -0
  145. package/lib/market/api/api.mock.js +454 -0
  146. package/lib/market/api/api.mock.js.map +1 -0
  147. package/lib/market/types.d.ts +83 -0
  148. package/lib/market/types.d.ts.map +1 -0
  149. package/lib/market/types.js +3 -0
  150. package/lib/market/types.js.map +1 -0
  151. package/lib/market/utils/countervalueFormatter.d.ts +8 -0
  152. package/lib/market/utils/countervalueFormatter.d.ts.map +1 -0
  153. package/lib/market/utils/countervalueFormatter.js +18 -0
  154. package/lib/market/utils/countervalueFormatter.js.map +1 -0
  155. package/lib/market/utils/rangeDataTable.d.ts +10 -0
  156. package/lib/market/utils/rangeDataTable.d.ts.map +1 -0
  157. package/lib/market/utils/rangeDataTable.js +36 -0
  158. package/lib/market/utils/rangeDataTable.js.map +1 -0
  159. package/package.json +7 -7
  160. package/src/__tests__/__snapshots__/all.libcore.ts.snap +144 -193
  161. package/src/__tests__/currencies.ts +1 -1
  162. package/src/__tests__/families/bitcoin/wallet-btc/xpub.synced.unit.test.ts +26 -25
  163. package/src/__tests__/families/bitcoin/wallet-btc/xpub.syncing.integration.test.ts +2 -0
  164. package/src/account/support.ts +1 -25
  165. package/src/api/Ethereum.ts +1 -4
  166. package/src/api/explorerConfig/index.ts +0 -4
  167. package/src/bridge/jsHelpers.ts +1 -1
  168. package/src/data/icons/react/index.tsx +1 -0
  169. package/src/data/icons/react/sol.tsx +1 -1
  170. package/src/data/icons/react/tyc.tsx +12 -0
  171. package/src/data/icons/reactNative/index.tsx +1 -0
  172. package/src/data/icons/reactNative/sol.tsx +1 -1
  173. package/src/data/icons/reactNative/tyc.tsx +13 -0
  174. package/src/data/icons/svg/SOL.svg +8 -1
  175. package/src/data/icons/svg/TYC.svg +6 -0
  176. package/src/env.ts +14 -0
  177. package/src/errors.ts +3 -0
  178. package/src/exchange/hw-app-exchange/Exchange.ts +22 -18
  179. package/src/exchange/index.ts +19 -1
  180. package/src/exchange/platform/completeExchange.ts +42 -0
  181. package/src/exchange/platform/serialization.ts +78 -0
  182. package/src/exchange/{swap → platform}/startExchange.ts +6 -2
  183. package/src/exchange/platform/transfer/completeExchange.ts +187 -0
  184. package/src/exchange/platform/transfer/index.ts +66 -0
  185. package/src/exchange/platform/types.ts +76 -0
  186. package/src/exchange/sell/checkSignatureAndPrepare.ts +2 -2
  187. package/src/exchange/sell/getTransactionId.ts +2 -2
  188. package/src/exchange/sell/index.ts +1 -1
  189. package/src/exchange/swap/completeExchange.ts +41 -50
  190. package/src/exchange/swap/initSwap.ts +4 -7
  191. package/src/exchange/swap/types.ts +1 -14
  192. package/src/exchange/testCurrencyConfig/data/coins.ts +20 -0
  193. package/src/exchange/testCurrencyConfig/index.ts +24 -0
  194. package/src/families/bitcoin/test-dataset.ts +1 -1
  195. package/src/families/bitcoin/wallet-btc/wallet.ts +8 -2
  196. package/src/families/ethereum/modules/send.ts +3 -0
  197. package/src/families/ethereum/specs.ts +2 -0
  198. package/src/families/tezos/bridge/js.ts +41 -40
  199. package/src/families/tezos/hw-getAddress.ts +1 -3
  200. package/src/families/tezos/synchronisation.ts +4 -3
  201. package/src/hw/actions/app.ts +17 -1
  202. package/src/hw/actions/completeExchange.ts +9 -3
  203. package/src/hw/connectApp.ts +19 -0
  204. package/src/market/MarketDataProvider.tsx +440 -0
  205. package/src/market/api/api.mock.ts +374 -0
  206. package/src/market/api/api.ts +227 -0
  207. package/src/market/types.ts +92 -0
  208. package/src/market/utils/countervalueFormatter.ts +24 -0
  209. package/src/market/utils/rangeDataTable.ts +40 -0
  210. package/lib/exchange/swap/startExchange.d.ts.map +0 -1
  211. package/lib/exchange/swap/startExchange.js.map +0 -1
@@ -30,6 +30,7 @@ import { getEnv } from "../../../env";
30
30
  import { signOperation } from "../signOperation";
31
31
  import { patchOperationWithHash } from "../../../operation";
32
32
  import { log } from "@ledgerhq/logs";
33
+ import { InvalidAddressBecauseAlreadyDelegated } from "../../../errors";
33
34
 
34
35
  const receive = makeAccountBridgeReceive();
35
36
 
@@ -70,54 +71,52 @@ const getTransactionStatus = async (
70
71
  const { tezosResources } = account;
71
72
  if (!tezosResources) throw new Error("tezosResources is missing");
72
73
 
73
- if (!t.taquitoError) {
74
- if (t.mode !== "undelegate") {
75
- if (account.freshAddress === t.recipient) {
76
- errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource();
77
- } else {
78
- const { recipientError, recipientWarning } = await validateRecipient(
79
- account.currency,
80
- t.recipient
81
- );
74
+ if (t.mode !== "undelegate") {
75
+ if (account.freshAddress === t.recipient) {
76
+ errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource();
77
+ } else {
78
+ const { recipientError, recipientWarning } = await validateRecipient(
79
+ account.currency,
80
+ t.recipient
81
+ );
82
82
 
83
- if (recipientError) {
84
- errors.recipient = recipientError;
85
- }
83
+ if (recipientError) {
84
+ errors.recipient = recipientError;
85
+ }
86
86
 
87
- if (recipientWarning) {
88
- warnings.recipient = recipientWarning;
89
- }
87
+ if (recipientWarning) {
88
+ warnings.recipient = recipientWarning;
90
89
  }
91
90
  }
91
+ }
92
92
 
93
- if (t.mode === "send") {
94
- if (!errors.amount && t.amount.eq(0)) {
95
- errors.amount = new AmountRequired();
96
- } else if (!errors.amount && t.amount.lt(0)) {
97
- errors.amount = new NotEnoughBalance();
98
- } else if (t.amount.gt(0) && estimatedFees.times(10).gt(t.amount)) {
99
- warnings.feeTooHigh = new FeeTooHigh();
100
- }
93
+ if (t.mode === "send") {
94
+ if (!errors.amount && t.amount.eq(0)) {
95
+ errors.amount = new AmountRequired();
96
+ } else if (!errors.amount && t.amount.lt(0)) {
97
+ errors.amount = new NotEnoughBalance();
98
+ } else if (t.amount.gt(0) && estimatedFees.times(10).gt(t.amount)) {
99
+ warnings.feeTooHigh = new FeeTooHigh();
100
+ }
101
101
 
102
- const thresholdWarning = 0.5 * 10 ** account.currency.units[0].magnitude;
103
-
104
- if (
105
- !errors.amount &&
106
- account.balance
107
- .minus(t.amount)
108
- .minus(estimatedFees)
109
- .lt(thresholdWarning)
110
- ) {
111
- if (isAccountDelegating(account)) {
112
- if (t.useAllAmount) {
113
- errors.amount = new RecommendUndelegation();
114
- } else {
115
- warnings.amount = new RecommendUndelegation();
116
- }
102
+ const thresholdWarning = 0.5 * 10 ** account.currency.units[0].magnitude;
103
+
104
+ if (
105
+ !errors.amount &&
106
+ account.balance.minus(t.amount).minus(estimatedFees).lt(thresholdWarning)
107
+ ) {
108
+ if (isAccountDelegating(account)) {
109
+ if (t.useAllAmount) {
110
+ errors.amount = new RecommendUndelegation();
111
+ } else {
112
+ warnings.amount = new RecommendUndelegation();
117
113
  }
118
114
  }
119
115
  }
120
- } else {
116
+ }
117
+
118
+ // if we also have taquitoError, we interprete them and they override the previously inferred errors
119
+ if (t.taquitoError) {
121
120
  log("taquitoerror", String(t.taquitoError));
122
121
 
123
122
  // remap taquito errors
@@ -127,7 +126,9 @@ const getTransactionStatus = async (
127
126
  } else {
128
127
  errors.amount = new NotEnoughBalanceToDelegate();
129
128
  }
130
- } else {
129
+ } else if (t.taquitoError.endsWith("delegate.unchanged")) {
130
+ errors.recipient = new InvalidAddressBecauseAlreadyDelegated();
131
+ } else if (!errors.amount) {
131
132
  // unidentified error case
132
133
  errors.amount = new Error(t.taquitoError);
133
134
  }
@@ -3,9 +3,7 @@ import type { Resolver } from "../../hw/getAddress/types";
3
3
 
4
4
  const resolver: Resolver = async (transport, { path, verify }) => {
5
5
  const xtz = new Xtz(transport);
6
- const r = await xtz.getAddress(path, {
7
- verify,
8
- });
6
+ const r = await xtz.getAddress(path, { verify });
9
7
  return { ...r, path };
10
8
  };
11
9
 
@@ -48,9 +48,10 @@ export const getAccountShape: GetAccountShape = async (infoInput) => {
48
48
  derivationMode,
49
49
  });
50
50
 
51
- const initialStableOperations = initialAccount
52
- ? initialAccount.operations
53
- : [];
51
+ const initialStableOperations =
52
+ initialAccount && initialAccount.id === accountId
53
+ ? initialAccount.operations
54
+ : [];
54
55
 
55
56
  // fetch transactions, incrementally if possible
56
57
  const mostRecentStableOperation = initialStableOperations[0];
@@ -40,6 +40,7 @@ import type { Device, Action } from "./types";
40
40
  import { shouldUpgrade } from "../../apps";
41
41
  import { ConnectAppTimeout } from "../../errors";
42
42
  import perFamilyAccount from "../../generated/account";
43
+ import type { DeviceInfo, FirmwareUpdateContext } from "../../types/manager";
43
44
 
44
45
  type State = {
45
46
  isLoading: boolean;
@@ -60,6 +61,8 @@ type State = {
60
61
  allowManagerRequestedWording: string | null | undefined;
61
62
  allowManagerGranted: boolean;
62
63
  device: Device | null | undefined;
64
+ deviceInfo?: DeviceInfo | null | undefined;
65
+ latestFirmware?: FirmwareUpdateContext | null | undefined;
63
66
  error: Error | null | undefined;
64
67
  derivation:
65
68
  | {
@@ -146,6 +149,8 @@ const getInitialState = (device?: Device | null | undefined): State => ({
146
149
  allowManagerRequestedWording: null,
147
150
  allowManagerGranted: false,
148
151
  device: null,
152
+ deviceInfo: null,
153
+ latestFirmware: null,
149
154
  opened: false,
150
155
  appAndVersion: null,
151
156
  error: null,
@@ -160,6 +165,13 @@ const reducer = (state: State, e: Event): State => {
160
165
  case "unresponsiveDevice":
161
166
  return { ...state, unresponsive: true };
162
167
 
168
+ case "device-update-last-seen":
169
+ return {
170
+ ...state,
171
+ deviceInfo: e.deviceInfo,
172
+ latestFirmware: e.latestFirmware,
173
+ };
174
+
163
175
  case "disconnected":
164
176
  return { ...getInitialState(), isLoading: !!e.expected };
165
177
 
@@ -585,7 +597,11 @@ export const createAction = (
585
597
  scan(reducer, getInitialState()), // tap((s) => console.log("connectApp state", s)),
586
598
  // we debounce the UI state to not blink on the UI
587
599
  debounce((s: State) => {
588
- if (s.allowOpeningRequestedWording || s.allowOpeningGranted) {
600
+ if (
601
+ s.allowOpeningRequestedWording ||
602
+ s.allowOpeningGranted ||
603
+ s.deviceInfo
604
+ ) {
589
605
  // no debounce for allow event
590
606
  return EMPTY;
591
607
  }
@@ -11,6 +11,7 @@ type State = {
11
11
  completeExchangeResult: Transaction | null | undefined;
12
12
  completeExchangeError: Error | null | undefined;
13
13
  freezeReduxDevice: boolean;
14
+ completeExchangeRequested: boolean;
14
15
  isLoading: boolean;
15
16
  };
16
17
 
@@ -61,14 +62,19 @@ const mapResult = ({
61
62
  const initialState: State = {
62
63
  completeExchangeResult: null,
63
64
  completeExchangeError: null,
65
+ completeExchangeRequested: false,
64
66
  freezeReduxDevice: false,
65
- isLoading: false,
67
+ isLoading: true,
66
68
  };
67
69
 
68
70
  const reducer = (state: State, e: ExchangeRequestEvent) => {
69
71
  switch (e.type) {
70
72
  case "complete-exchange":
71
- return { ...state, isLoading: true, freezeReduxDevice: true };
73
+ return {
74
+ ...state,
75
+ completeExchangeStarted: true,
76
+ freezeReduxDevice: true,
77
+ };
72
78
 
73
79
  case "complete-exchange-error":
74
80
  return {
@@ -81,7 +87,7 @@ const reducer = (state: State, e: ExchangeRequestEvent) => {
81
87
  return {
82
88
  ...state,
83
89
  completeExchangeRequested: true,
84
- isLoading: true,
90
+ isLoading: false,
85
91
  };
86
92
  case "complete-exchange-result":
87
93
  return {
@@ -55,6 +55,11 @@ export type ConnectAppEvent =
55
55
  type: "disconnected";
56
56
  expected?: boolean;
57
57
  }
58
+ | {
59
+ type: "device-update-last-seen";
60
+ deviceInfo: DeviceInfo;
61
+ latestFirmware: FirmwareUpdateContext | null | undefined;
62
+ }
58
63
  | {
59
64
  type: "device-permission-requested";
60
65
  wording: string;
@@ -103,6 +108,20 @@ export const openAppFromDashboard = (
103
108
  appName: string
104
109
  ): Observable<ConnectAppEvent> =>
105
110
  concat(
111
+ // Nb Allows LLD/LLM to update lastSeenDevice
112
+ from(getDeviceInfo(transport)).pipe(
113
+ mergeMap((deviceInfo) =>
114
+ from(manager.getLatestFirmwareForDevice(deviceInfo)).pipe(
115
+ concatMap((latestFirmware) =>
116
+ of<ConnectAppEvent>({
117
+ type: "device-update-last-seen",
118
+ deviceInfo,
119
+ latestFirmware,
120
+ })
121
+ )
122
+ )
123
+ )
124
+ ),
106
125
  of<ConnectAppEvent>({
107
126
  type: "ask-open-app",
108
127
  appName,
@@ -0,0 +1,440 @@
1
+ /* eslint-disable @typescript-eslint/no-empty-function */
2
+ // @flow
3
+ import React, {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ ReactElement,
8
+ useReducer,
9
+ useEffect,
10
+ useState,
11
+ } from "react";
12
+
13
+ import { useDebounce } from "../hooks/useDebounce";
14
+ import { Currency } from "../types";
15
+ import {
16
+ State,
17
+ MarketDataApi,
18
+ MarketListRequestParams,
19
+ MarketCurrencyChartDataRequestParams,
20
+ CurrencyData,
21
+ SingleCoinState,
22
+ } from "./types";
23
+ import defaultFetchApi from "./api/api";
24
+
25
+ type Props = {
26
+ children: React.ReactNode;
27
+ fetchApi?: MarketDataApi;
28
+ countervalue?: Currency;
29
+ initState?: Partial<State>;
30
+ };
31
+ type API = {
32
+ refresh: (param?: MarketListRequestParams) => void;
33
+ refreshChart: (param?: MarketCurrencyChartDataRequestParams) => void;
34
+ selectCurrency: (key: string) => void;
35
+ loadNextPage: () => Promise<boolean>;
36
+ setCounterCurrency: (counterCurrency: string) => void;
37
+ };
38
+
39
+ export type MarketDataContextType = State & API;
40
+
41
+ const initialState: State = {
42
+ ready: false,
43
+ marketData: [],
44
+ selectedCurrency: undefined,
45
+ requestParams: {
46
+ range: "24h",
47
+ limit: 50,
48
+ ids: [],
49
+ starred: [],
50
+ orderBy: "market_cap",
51
+ order: "desc",
52
+ search: "",
53
+ liveCompatible: false,
54
+ },
55
+ page: 1,
56
+ chartRequestParams: {
57
+ range: "24h",
58
+ },
59
+ loading: false,
60
+ loadingChart: false,
61
+ endOfList: false,
62
+ error: undefined,
63
+ totalCoinsAvailable: 0,
64
+ supportedCounterCurrencies: [],
65
+ selectedCoinData: undefined,
66
+ counterCurrency: undefined,
67
+ };
68
+
69
+ const MarketDataContext = createContext<MarketDataContextType>({
70
+ ...initialState,
71
+ refresh: () => {},
72
+ refreshChart: () => {},
73
+ selectCurrency: () => {},
74
+ loadNextPage: () => Promise.resolve(true),
75
+ setCounterCurrency: () => {},
76
+ });
77
+
78
+ const ACTIONS = {
79
+ IS_READY: "IS_READY",
80
+
81
+ UPDATE_MARKET_DATA: "UPDATE_MARKET_DATA",
82
+ UPDATE_SINGLE_MARKET_DATA: "UPDATE_SINGLE_MARKET_DATA",
83
+ UPDATE_SINGLE_CHART_DATA: "UPDATE_SINGLE_CHART_DATA",
84
+
85
+ REFRESH_MARKET_DATA: "REFRESH_MARKET_DATA",
86
+ REFRESH_CHART_DATA: "REFRESH_CHART_DATA",
87
+
88
+ SET_LOADING: "SET_LOADING",
89
+ SET_LOADING_CHART: "SET_LOADING_CHART",
90
+ SET_ERROR: "SET_ERROR",
91
+
92
+ SELECT_CURRENCY: "SELECT_CURRENCY",
93
+ UPDATE_COUNTERVALUE: "UPDATE_COUNTERVALUE",
94
+ };
95
+
96
+ function marketDataReducer(state, action) {
97
+ switch (action.type) {
98
+ case ACTIONS.IS_READY:
99
+ return { ...state, ...action.payload, isReady: true };
100
+ case ACTIONS.UPDATE_MARKET_DATA: {
101
+ const newData = action.payload.marketData;
102
+ const page = action.payload.page || state.requestParams.page;
103
+ const marketData = [...state.marketData.map((data) => ({ ...data }))];
104
+ if (!newData.length || marketData.some(({ id }) => id === newData[0].id))
105
+ return { ...state, loading: false };
106
+
107
+ return {
108
+ ...state,
109
+ marketData: marketData.concat(newData),
110
+ endOfList: newData.length < state.requestParams.limit,
111
+ loading: false,
112
+ page,
113
+ };
114
+ }
115
+ case ACTIONS.UPDATE_SINGLE_MARKET_DATA: {
116
+ const marketData = [...state.marketData.map((data) => ({ ...data }))];
117
+ const index = marketData.findIndex(({ id }) => id === action.payload.id);
118
+ if (index >= 0) {
119
+ marketData[index] = { ...marketData[index], ...action.payload };
120
+ }
121
+ return { ...state, marketData, loading: false };
122
+ }
123
+ case ACTIONS.UPDATE_SINGLE_CHART_DATA: {
124
+ const marketData = [...state.marketData.map((data) => ({ ...data }))];
125
+ const index = marketData.findIndex(({ id }) => id === action.payload.id);
126
+ if (index >= 0) {
127
+ marketData[index].chartData = {
128
+ ...marketData[index].chartData,
129
+ ...action.payload.chartData,
130
+ };
131
+ }
132
+ return { ...state, marketData, loading: false };
133
+ }
134
+
135
+ case ACTIONS.REFRESH_MARKET_DATA: {
136
+ const requestParams = {
137
+ ...state.requestParams,
138
+ ...action.payload,
139
+ lastRequestTime: Date.now(),
140
+ };
141
+ return {
142
+ ...state,
143
+ marketData: [],
144
+ requestParams,
145
+ loading: true,
146
+ page: 1,
147
+ };
148
+ }
149
+ case ACTIONS.REFRESH_CHART_DATA: {
150
+ const chartRequestParams = {
151
+ ...state.chartRequestParams,
152
+ ...action.payload,
153
+ loadingChart: true,
154
+ lastRequestTime: Date.now(),
155
+ };
156
+ return { ...state, chartRequestParams };
157
+ }
158
+
159
+ case ACTIONS.UPDATE_COUNTERVALUE: {
160
+ const requestParams = {
161
+ ...state.requestParams,
162
+ lastRequestTime: Date.now(),
163
+ page: 1,
164
+ counterCurrency: action.payload,
165
+ };
166
+ const chartRequestParams = {
167
+ ...state.chartRequestParams,
168
+ lastRequestTime: Date.now(),
169
+ counterCurrency: action.payload,
170
+ };
171
+ return {
172
+ ...state,
173
+ counterCurrency: action.payload,
174
+ marketData: [],
175
+ requestParams,
176
+ chartRequestParams,
177
+ loading: true,
178
+ };
179
+ }
180
+
181
+ case ACTIONS.SET_LOADING:
182
+ return { ...state, loading: action.payload };
183
+ case ACTIONS.SET_LOADING_CHART:
184
+ return { ...state, loadingChart: action.payload };
185
+ case ACTIONS.SET_ERROR:
186
+ return { ...state, error: action.payload, loading: false };
187
+ case ACTIONS.SELECT_CURRENCY:
188
+ return {
189
+ ...state,
190
+ selectedCurrency: action.payload,
191
+ loading: !!action.payload,
192
+ };
193
+ default:
194
+ return state;
195
+ }
196
+ }
197
+
198
+ export const MarketDataProvider = ({
199
+ children,
200
+ fetchApi,
201
+ countervalue,
202
+ initState = {},
203
+ }: Props): ReactElement => {
204
+ const [state, dispatch] = useReducer(marketDataReducer, {
205
+ ...initialState,
206
+ ...initState,
207
+ });
208
+ const api = fetchApi || defaultFetchApi;
209
+ const {
210
+ marketData,
211
+ requestParams,
212
+ chartRequestParams,
213
+ loading,
214
+ loadingChart,
215
+ page,
216
+ } = useDebounce(state, 300);
217
+
218
+ const handleError = useCallback((payload) => {
219
+ dispatch({ type: ACTIONS.SET_ERROR, payload });
220
+ }, []);
221
+
222
+ useEffect(() => {
223
+ if (countervalue)
224
+ api.supportedCounterCurrencies().then(
225
+ (supportedCounterCurrencies) =>
226
+ api.setSupportedCoinsList().then((coins) => {
227
+ dispatch({
228
+ type: ACTIONS.IS_READY,
229
+ payload: {
230
+ totalCoinsAvailable: coins.length,
231
+ supportedCounterCurrencies,
232
+ },
233
+ });
234
+ dispatch({
235
+ type: ACTIONS.UPDATE_COUNTERVALUE,
236
+ payload: supportedCounterCurrencies.includes(countervalue.ticker)
237
+ ? countervalue.ticker
238
+ : "usd",
239
+ });
240
+ }, handleError),
241
+ handleError
242
+ );
243
+ }, [api, countervalue, handleError]);
244
+
245
+ useEffect(() => {
246
+ if (
247
+ chartRequestParams?.id &&
248
+ chartRequestParams?.counterCurrency &&
249
+ !loadingChart
250
+ ) {
251
+ const currentMarketData = marketData.find(
252
+ ({ id }: CurrencyData) => id === chartRequestParams.id
253
+ );
254
+ const range = chartRequestParams.range;
255
+
256
+ if (currentMarketData && !currentMarketData?.chartData?.[range]) {
257
+ api.currencyChartData(chartRequestParams).then(
258
+ (chartData) =>
259
+ dispatch({
260
+ type: ACTIONS.UPDATE_SINGLE_CHART_DATA,
261
+ payload: { id: chartRequestParams.id, chartData },
262
+ }),
263
+ handleError
264
+ );
265
+ } else
266
+ dispatch({
267
+ type: ACTIONS.SET_LOADING_CHART,
268
+ payload: false,
269
+ });
270
+ }
271
+ }, [chartRequestParams, marketData, loadingChart, api, handleError]);
272
+
273
+ useEffect(() => {
274
+ if (
275
+ chartRequestParams?.id &&
276
+ chartRequestParams?.counterCurrency &&
277
+ !loading
278
+ ) {
279
+ dispatch({ type: ACTIONS.SET_LOADING, payload: true });
280
+ api
281
+ .listPaginated({
282
+ ...chartRequestParams,
283
+ search: "",
284
+ starred: [],
285
+ liveCompatible: false,
286
+ ids: [chartRequestParams.id],
287
+ limit: 1,
288
+ page: 1,
289
+ })
290
+ .then(
291
+ ([{ chartData: _, ...marketData }]) =>
292
+ dispatch({
293
+ type: ACTIONS.UPDATE_SINGLE_MARKET_DATA,
294
+ payload: marketData,
295
+ }),
296
+ handleError
297
+ );
298
+ }
299
+ }, [api, chartRequestParams, handleError, loading]);
300
+
301
+ useEffect(() => {
302
+ if (requestParams?.counterCurrency) {
303
+ api.listPaginated(requestParams).then(
304
+ (marketData) =>
305
+ dispatch({
306
+ type: ACTIONS.UPDATE_MARKET_DATA,
307
+ payload: { marketData },
308
+ }),
309
+ handleError
310
+ );
311
+ }
312
+ }, [api, handleError, requestParams]);
313
+
314
+ const refresh = useCallback((payload = {}) => {
315
+ dispatch({ type: ACTIONS.REFRESH_MARKET_DATA, payload });
316
+ }, []);
317
+
318
+ const refreshChart = useCallback((payload = {}) => {
319
+ dispatch({ type: ACTIONS.REFRESH_CHART_DATA, payload });
320
+ }, []);
321
+
322
+ const selectCurrency = useCallback((id) => {
323
+ dispatch({
324
+ type: ACTIONS.REFRESH_CHART_DATA,
325
+ payload: {
326
+ id,
327
+ },
328
+ });
329
+ }, []);
330
+
331
+ const loadNextPage = useCallback(() => {
332
+ return new Promise((resolve, reject) => {
333
+ if (loading) {
334
+ reject(new Error());
335
+ } else {
336
+ const newPage = page + 1;
337
+ api.listPaginated({ ...requestParams, page: newPage }).then(
338
+ (marketData) => {
339
+ dispatch({
340
+ type: ACTIONS.UPDATE_MARKET_DATA,
341
+ payload: { marketData, page: newPage },
342
+ });
343
+ resolve(true);
344
+ },
345
+ (err) => {
346
+ handleError(err);
347
+ reject(new Error(err));
348
+ }
349
+ );
350
+ }
351
+ });
352
+ }, [loading, page, api, requestParams, handleError]);
353
+
354
+ const setCounterCurrency = useCallback(
355
+ (payload) => dispatch({ type: ACTIONS.UPDATE_COUNTERVALUE, payload }),
356
+ [dispatch]
357
+ );
358
+
359
+ const value = {
360
+ ...state,
361
+ refresh,
362
+ refreshChart,
363
+ selectCurrency,
364
+ loadNextPage,
365
+ setCounterCurrency,
366
+ };
367
+
368
+ return (
369
+ <MarketDataContext.Provider value={value}>
370
+ {children}
371
+ </MarketDataContext.Provider>
372
+ );
373
+ };
374
+
375
+ export function useMarketData(): MarketDataContextType {
376
+ return useContext(MarketDataContext);
377
+ }
378
+
379
+ type SingleCoinProviderData = SingleCoinState & {
380
+ refreshChart: (param?: MarketCurrencyChartDataRequestParams) => void;
381
+ setCounterCurrency: (counterCurrency: string) => void;
382
+ };
383
+
384
+ export function useSingleCoinMarketData(
385
+ currencyId: string
386
+ ): SingleCoinProviderData {
387
+ const {
388
+ marketData,
389
+ selectedCurrency,
390
+ chartRequestParams,
391
+ loading,
392
+ loadingChart,
393
+ error,
394
+ counterCurrency,
395
+ refreshChart,
396
+ setCounterCurrency,
397
+ supportedCounterCurrencies,
398
+ } = useContext(MarketDataContext);
399
+
400
+ const [state, setState] = useState<SingleCoinProviderData>({
401
+ selectedCoinData: undefined,
402
+ selectedCurrency,
403
+ chartRequestParams,
404
+ loading,
405
+ loadingChart,
406
+ error,
407
+ counterCurrency,
408
+ refreshChart,
409
+ setCounterCurrency,
410
+ supportedCounterCurrencies,
411
+ });
412
+
413
+ useEffect(() => {
414
+ if (marketData)
415
+ setState((s) => ({
416
+ ...s,
417
+ selectedCoinData:
418
+ marketData.find(({ id }) => id === currencyId) || s.selectedCoinData,
419
+ selectedCurrency,
420
+ chartRequestParams,
421
+ loading,
422
+ loadingChart,
423
+ error,
424
+ counterCurrency,
425
+ supportedCounterCurrencies,
426
+ }));
427
+ }, [
428
+ marketData,
429
+ currencyId,
430
+ selectedCurrency,
431
+ chartRequestParams,
432
+ loading,
433
+ loadingChart,
434
+ error,
435
+ counterCurrency,
436
+ supportedCounterCurrencies,
437
+ ]);
438
+
439
+ return state;
440
+ }