@ledgerhq/live-common 34.53.0-nightly.20251112023812 → 34.53.0-nightly.20251113023835
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/bridge/generic-alpaca/buildSubAccounts.js +4 -4
- package/lib/bridge/generic-alpaca/buildSubAccounts.js.map +1 -1
- package/lib/bridge/useBridgeTransaction.d.ts +4 -0
- package/lib/bridge/useBridgeTransaction.d.ts.map +1 -1
- package/lib/bridge/useBridgeTransaction.js +48 -5
- package/lib/bridge/useBridgeTransaction.js.map +1 -1
- package/lib/e2e/index.d.ts +166 -1
- package/lib/e2e/index.d.ts.map +1 -1
- package/lib/e2e/index.js +2 -2
- package/lib/e2e/index.js.map +1 -1
- package/lib-es/bridge/generic-alpaca/buildSubAccounts.js +4 -4
- package/lib-es/bridge/generic-alpaca/buildSubAccounts.js.map +1 -1
- package/lib-es/bridge/useBridgeTransaction.d.ts +4 -0
- package/lib-es/bridge/useBridgeTransaction.d.ts.map +1 -1
- package/lib-es/bridge/useBridgeTransaction.js +46 -4
- package/lib-es/bridge/useBridgeTransaction.js.map +1 -1
- package/lib-es/e2e/index.d.ts +166 -1
- package/lib-es/e2e/index.d.ts.map +1 -1
- package/lib-es/e2e/index.js +2 -2
- package/lib-es/e2e/index.js.map +1 -1
- package/package.json +48 -48
- package/src/bridge/generic-alpaca/buildSubAccounts.test.ts +66 -0
- package/src/bridge/generic-alpaca/buildSubAccounts.ts +5 -5
- package/src/bridge/useBridgeTransaction.test.ts +64 -0
- package/src/bridge/useBridgeTransaction.ts +70 -4
- package/src/e2e/index.ts +2 -1
- package/src/exchange/swap/hooks/useFromState.test.ts +19 -0
|
@@ -12,6 +12,7 @@ import useBridgeTransaction, {
|
|
|
12
12
|
} from "./useBridgeTransaction";
|
|
13
13
|
import { setSupportedCurrencies } from "../currencies";
|
|
14
14
|
import { LiveConfig } from "@ledgerhq/live-config/LiveConfig";
|
|
15
|
+
import { shouldSyncBeforeTx } from "./useBridgeTransaction";
|
|
15
16
|
|
|
16
17
|
const BTC = getCryptoCurrencyById("bitcoin");
|
|
17
18
|
|
|
@@ -26,6 +27,13 @@ LiveConfig.setConfig({
|
|
|
26
27
|
},
|
|
27
28
|
});
|
|
28
29
|
|
|
30
|
+
jest.mock("@ledgerhq/live-config/LiveConfig", () => ({
|
|
31
|
+
LiveConfig: {
|
|
32
|
+
getValueByKey: jest.fn(),
|
|
33
|
+
setConfig: jest.fn(),
|
|
34
|
+
},
|
|
35
|
+
}));
|
|
36
|
+
|
|
29
37
|
describe("useBridgeTransaction", () => {
|
|
30
38
|
test("initialize with a BTC account settles the transaction", async () => {
|
|
31
39
|
const mainAccount = genAccount("mocked-account-1", { currency: BTC });
|
|
@@ -78,4 +86,60 @@ describe("useBridgeTransaction", () => {
|
|
|
78
86
|
setGlobalOnBridgeError(before);
|
|
79
87
|
}
|
|
80
88
|
});
|
|
89
|
+
|
|
90
|
+
describe("shouldSyncBeforeTx", () => {
|
|
91
|
+
const mockCurrency = { id: "btc" } as any;
|
|
92
|
+
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
jest.clearAllMocks();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("returns true when currency-specific config has syncBeforeTx = true", () => {
|
|
98
|
+
(LiveConfig.getValueByKey as jest.Mock).mockImplementation((key: string) => {
|
|
99
|
+
if (key === "config_currency_btc") return { syncBeforeTx: true };
|
|
100
|
+
return null;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const result = shouldSyncBeforeTx(mockCurrency);
|
|
104
|
+
expect(result).toBe(true);
|
|
105
|
+
expect(LiveConfig.getValueByKey).toHaveBeenCalledWith("config_currency_btc");
|
|
106
|
+
expect(LiveConfig.getValueByKey).toHaveBeenCalledWith("config_currency");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("returns false when currency-specific config has syncBeforeTx = false", () => {
|
|
110
|
+
(LiveConfig.getValueByKey as jest.Mock).mockImplementation((key: string) => {
|
|
111
|
+
if (key === "config_currency_btc") return { syncBeforeTx: false };
|
|
112
|
+
return null;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const result = shouldSyncBeforeTx(mockCurrency);
|
|
116
|
+
expect(result).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("returns true when shared config has syncBeforeTx = true and no currency-specific config", () => {
|
|
120
|
+
(LiveConfig.getValueByKey as jest.Mock).mockImplementation((key: string) => {
|
|
121
|
+
if (key === "config_currency") return { syncBeforeTx: true };
|
|
122
|
+
return null;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const result = shouldSyncBeforeTx(mockCurrency);
|
|
126
|
+
expect(result).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("returns false when neither config has syncBeforeTx", () => {
|
|
130
|
+
(LiveConfig.getValueByKey as jest.Mock).mockReturnValue({});
|
|
131
|
+
const result = shouldSyncBeforeTx(mockCurrency);
|
|
132
|
+
expect(result).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("returns false when shared config has syncBeforeTx = false", () => {
|
|
136
|
+
(LiveConfig.getValueByKey as jest.Mock).mockImplementation((key: string) => {
|
|
137
|
+
if (key === "config_currency") return { syncBeforeTx: false };
|
|
138
|
+
return null;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const result = shouldSyncBeforeTx(mockCurrency);
|
|
142
|
+
expect(result).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
81
145
|
});
|
|
@@ -6,6 +6,8 @@ import { getMainAccount } from "../account";
|
|
|
6
6
|
import { delay } from "../promise";
|
|
7
7
|
import type { Account, AccountBridge, AccountLike } from "@ledgerhq/types-live";
|
|
8
8
|
import type { Transaction, TransactionStatus } from "../generated/types";
|
|
9
|
+
import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
|
|
10
|
+
import { LiveConfig } from "@ledgerhq/live-config/LiveConfig";
|
|
9
11
|
|
|
10
12
|
export type State<T extends Transaction = Transaction> = {
|
|
11
13
|
account: AccountLike | null | undefined;
|
|
@@ -15,6 +17,8 @@ export type State<T extends Transaction = Transaction> = {
|
|
|
15
17
|
statusOnTransaction: T | null | undefined;
|
|
16
18
|
errorAccount: Error | null | undefined;
|
|
17
19
|
errorStatus: Error | null | undefined;
|
|
20
|
+
syncing: boolean;
|
|
21
|
+
synced: boolean;
|
|
18
22
|
};
|
|
19
23
|
|
|
20
24
|
export type Result<T extends Transaction = Transaction> = {
|
|
@@ -55,6 +59,12 @@ type Actions<T extends Transaction = Transaction> =
|
|
|
55
59
|
| {
|
|
56
60
|
type: "setTransaction";
|
|
57
61
|
transaction: T;
|
|
62
|
+
}
|
|
63
|
+
| {
|
|
64
|
+
type: "onStartSync";
|
|
65
|
+
}
|
|
66
|
+
| {
|
|
67
|
+
type: "onSync";
|
|
58
68
|
};
|
|
59
69
|
|
|
60
70
|
type Reducer<T extends Transaction = Transaction> = (
|
|
@@ -76,6 +86,18 @@ const initial: State<Transaction> = {
|
|
|
76
86
|
statusOnTransaction: null,
|
|
77
87
|
errorAccount: null,
|
|
78
88
|
errorStatus: null,
|
|
89
|
+
syncing: false,
|
|
90
|
+
synced: false,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const shouldSyncBeforeTx = (currency: CryptoCurrency): boolean => {
|
|
94
|
+
const currencyConfig = LiveConfig.getValueByKey(`config_currency_${currency.id}`);
|
|
95
|
+
const sharedConfig = LiveConfig.getValueByKey("config_currency");
|
|
96
|
+
if (currencyConfig && "syncBeforeTx" in currencyConfig) {
|
|
97
|
+
return currencyConfig.syncBeforeTx === true;
|
|
98
|
+
} else {
|
|
99
|
+
return sharedConfig && "syncBeforeTx" in sharedConfig && sharedConfig.syncBeforeTx === true;
|
|
100
|
+
}
|
|
79
101
|
};
|
|
80
102
|
|
|
81
103
|
const makeInit =
|
|
@@ -142,6 +164,8 @@ const reducer = <T extends Transaction = Transaction>(
|
|
|
142
164
|
account,
|
|
143
165
|
parentAccount,
|
|
144
166
|
transaction: t,
|
|
167
|
+
syncing: false,
|
|
168
|
+
synced: false,
|
|
145
169
|
} as State<T>;
|
|
146
170
|
} catch (e: any) {
|
|
147
171
|
return {
|
|
@@ -149,6 +173,8 @@ const reducer = <T extends Transaction = Transaction>(
|
|
|
149
173
|
account,
|
|
150
174
|
parentAccount,
|
|
151
175
|
errorAccount: e,
|
|
176
|
+
syncing: false,
|
|
177
|
+
synced: false,
|
|
152
178
|
} as State<T>;
|
|
153
179
|
}
|
|
154
180
|
}
|
|
@@ -177,6 +203,12 @@ const reducer = <T extends Transaction = Transaction>(
|
|
|
177
203
|
if (action.error === state.errorStatus) return state;
|
|
178
204
|
return { ...state, errorStatus: action.error };
|
|
179
205
|
|
|
206
|
+
case "onStartSync":
|
|
207
|
+
return { ...state, syncing: true, synced: false };
|
|
208
|
+
|
|
209
|
+
case "onSync":
|
|
210
|
+
return { ...state, syncing: false, synced: true };
|
|
211
|
+
|
|
180
212
|
default:
|
|
181
213
|
return state;
|
|
182
214
|
}
|
|
@@ -190,7 +222,17 @@ const useBridgeTransaction = <T extends Transaction = Transaction>(
|
|
|
190
222
|
optionalInit?: (() => Partial<State<T>>) | null | undefined,
|
|
191
223
|
): Result<T> => {
|
|
192
224
|
const [
|
|
193
|
-
{
|
|
225
|
+
{
|
|
226
|
+
account,
|
|
227
|
+
parentAccount,
|
|
228
|
+
transaction,
|
|
229
|
+
status,
|
|
230
|
+
statusOnTransaction,
|
|
231
|
+
syncing,
|
|
232
|
+
synced,
|
|
233
|
+
errorAccount,
|
|
234
|
+
errorStatus,
|
|
235
|
+
},
|
|
194
236
|
dispatch,
|
|
195
237
|
] = useReducer(reducer as Reducer<T>, undefined, makeInit<T>(optionalInit));
|
|
196
238
|
const setAccount = useCallback(
|
|
@@ -223,15 +265,39 @@ const useBridgeTransaction = <T extends Transaction = Transaction>(
|
|
|
223
265
|
const mainAccount = account ? getMainAccount(account, parentAccount) : null;
|
|
224
266
|
const errorDelay = useRef(INITIAL_ERROR_RETRY_DELAY);
|
|
225
267
|
const statusIsPending = useRef(false); // Stores if status already being processed
|
|
268
|
+
const shouldSync = mainAccount && shouldSyncBeforeTx(mainAccount.currency);
|
|
269
|
+
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
if (mainAccount === null || synced || syncing) return;
|
|
272
|
+
|
|
273
|
+
if (!shouldSync) return; // skip sync if not required by currency config
|
|
274
|
+
|
|
275
|
+
dispatch({ type: "onStartSync" });
|
|
276
|
+
const bridge = getAccountBridge(mainAccount, null);
|
|
277
|
+
const sub = bridge.sync(mainAccount, { paginationConfig: {} }).subscribe({
|
|
278
|
+
error: (_: Error) => {
|
|
279
|
+
// we do not block the user in case of error for now but it should be the case
|
|
280
|
+
dispatch({ type: "onSync" });
|
|
281
|
+
},
|
|
282
|
+
complete: () => {
|
|
283
|
+
dispatch({ type: "onSync" });
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return () => {
|
|
288
|
+
sub.unsubscribe();
|
|
289
|
+
};
|
|
290
|
+
}, [mainAccount, synced, syncing, shouldSync]);
|
|
226
291
|
|
|
227
292
|
const bridgePending = transaction !== statusOnTransaction;
|
|
293
|
+
|
|
228
294
|
// when transaction changes, prepare the transaction
|
|
229
295
|
useEffect(() => {
|
|
230
296
|
let ignore = false;
|
|
231
297
|
let errorTimeout: NodeJS.Timeout | null;
|
|
232
298
|
// If bridge is not pending, transaction change is due to
|
|
233
299
|
// the last onStatus dispatch (prepareTransaction changed original transaction) and must be ignored
|
|
234
|
-
if (!bridgePending) return;
|
|
300
|
+
if (!bridgePending && !synced) return;
|
|
235
301
|
|
|
236
302
|
if (mainAccount && transaction) {
|
|
237
303
|
// We don't debounce first status refresh, but any subsequent to avoid multiple calls
|
|
@@ -301,7 +367,7 @@ const useBridgeTransaction = <T extends Transaction = Transaction>(
|
|
|
301
367
|
errorTimeout = null;
|
|
302
368
|
}
|
|
303
369
|
};
|
|
304
|
-
}, [transaction, mainAccount, bridgePending, dispatch]);
|
|
370
|
+
}, [transaction, mainAccount, bridgePending, dispatch, synced]);
|
|
305
371
|
|
|
306
372
|
const bridgeError = errorAccount || errorStatus;
|
|
307
373
|
|
|
@@ -320,7 +386,7 @@ const useBridgeTransaction = <T extends Transaction = Transaction>(
|
|
|
320
386
|
parentAccount,
|
|
321
387
|
setAccount,
|
|
322
388
|
bridgeError,
|
|
323
|
-
bridgePending,
|
|
389
|
+
bridgePending: bridgePending && (shouldSync ? !synced : true),
|
|
324
390
|
};
|
|
325
391
|
};
|
|
326
392
|
|
package/src/e2e/index.ts
CHANGED
|
@@ -4,11 +4,12 @@ import { getFeature, DEFAULT_FEATURES } from "../featureFlags";
|
|
|
4
4
|
|
|
5
5
|
export const getAllFeatureFlags = (
|
|
6
6
|
appLanguage?: string,
|
|
7
|
+
localOverrides?: { [key in FeatureId]?: Feature | undefined },
|
|
7
8
|
): Partial<{ [key in FeatureId]: Feature }> => {
|
|
8
9
|
const res: Partial<{ [key in FeatureId]: Feature }> = {};
|
|
9
10
|
Object.keys(DEFAULT_FEATURES).forEach(k => {
|
|
10
11
|
const key = k as keyof typeof DEFAULT_FEATURES;
|
|
11
|
-
const value = getFeature({ key, appLanguage });
|
|
12
|
+
const value = getFeature({ key, appLanguage, localOverrides });
|
|
12
13
|
if (value !== null) res[key] = value;
|
|
13
14
|
});
|
|
14
15
|
return res;
|
|
@@ -11,9 +11,28 @@ import useBridgeTransaction from "../../../bridge/useBridgeTransaction";
|
|
|
11
11
|
import { genTokenAccount } from "@ledgerhq/coin-framework/mocks/account";
|
|
12
12
|
import { genAccount } from "../../../mock/account";
|
|
13
13
|
import { useFromState } from "./useFromState";
|
|
14
|
+
import { LiveConfig } from "@ledgerhq/live-config/LiveConfig";
|
|
14
15
|
|
|
15
16
|
const BTC = getCryptoCurrencyById("bitcoin");
|
|
16
17
|
const ETH = getCryptoCurrencyById("ethereum");
|
|
18
|
+
LiveConfig.setConfig({
|
|
19
|
+
config_currency_bitcoin: {
|
|
20
|
+
type: "object",
|
|
21
|
+
default: {
|
|
22
|
+
status: {
|
|
23
|
+
type: "active",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
config_currency_ethereum: {
|
|
28
|
+
type: "object",
|
|
29
|
+
default: {
|
|
30
|
+
status: {
|
|
31
|
+
type: "active",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
17
36
|
const USDT = {
|
|
18
37
|
type: "TokenCurrency" as const,
|
|
19
38
|
id: "ethereum/erc20/usd_tether__erc20_",
|