@ledgerhq/coin-tron 5.6.0-nightly.4 → 5.6.0-nightly.6

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 (31) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.unimportedrc.json +2 -1
  3. package/CHANGELOG.md +18 -0
  4. package/lib/bridge/getEstimateFees.test.js +27 -19
  5. package/lib/bridge/getEstimateFees.test.js.map +1 -1
  6. package/lib/bridge/synchronization.d.ts.map +1 -1
  7. package/lib/bridge/synchronization.integ.test.js +4 -4
  8. package/lib/bridge/synchronization.integ.test.js.map +1 -1
  9. package/lib/bridge/synchronization.js +49 -52
  10. package/lib/bridge/synchronization.js.map +1 -1
  11. package/lib/bridge/synchronization.test.js +87 -4
  12. package/lib/bridge/synchronization.test.js.map +1 -1
  13. package/lib/test/bot-specs.js +2 -2
  14. package/lib/test/bot-specs.js.map +1 -1
  15. package/lib-es/bridge/getEstimateFees.test.js +27 -19
  16. package/lib-es/bridge/getEstimateFees.test.js.map +1 -1
  17. package/lib-es/bridge/synchronization.d.ts.map +1 -1
  18. package/lib-es/bridge/synchronization.integ.test.js +1 -1
  19. package/lib-es/bridge/synchronization.integ.test.js.map +1 -1
  20. package/lib-es/bridge/synchronization.js +49 -52
  21. package/lib-es/bridge/synchronization.js.map +1 -1
  22. package/lib-es/bridge/synchronization.test.js +84 -1
  23. package/lib-es/bridge/synchronization.test.js.map +1 -1
  24. package/lib-es/test/bot-specs.js +1 -1
  25. package/lib-es/test/bot-specs.js.map +1 -1
  26. package/package.json +5 -5
  27. package/src/bridge/getEstimateFees.test.ts +28 -19
  28. package/src/bridge/synchronization.integ.test.ts +1 -1
  29. package/src/bridge/synchronization.test.ts +92 -1
  30. package/src/bridge/synchronization.ts +75 -80
  31. package/src/test/bot-specs.ts +1 -1
@@ -6,15 +6,18 @@ import { ACTIVATION_FEES, STANDARD_FEES_NATIVE, STANDARD_FEES_TRC_20 } from "../
6
6
  import { Account, TokenAccount } from "@ledgerhq/types-live";
7
7
  import { Transaction } from "../types";
8
8
 
9
+ // Mock typed functions
10
+ const mockGetAccount = jest.mocked(getAccount);
11
+ const mockEstimateFees = jest.mocked(estimateFees);
12
+ const mockExtractBandwidthInfo = jest.mocked(extractBandwidthInfo);
13
+
9
14
  jest.mock("./utils", () => ({
10
15
  extractBandwidthInfo: jest.fn(),
11
16
  getEstimatedBlockSize: jest.fn().mockReturnValue(new BigNumber(200)),
12
17
  }));
13
18
 
14
- jest.mock("../logic", () => ({
15
- estimateFees: jest.fn(),
16
- getAccount: jest.fn(),
17
- }));
19
+ jest.mock("../logic/estimateFees");
20
+ jest.mock("../logic/getAccount");
18
21
 
19
22
  describe("getEstimatedFees", () => {
20
23
  const mockAccount = {
@@ -56,8 +59,8 @@ describe("getEstimatedFees", () => {
56
59
 
57
60
  describe("getFeesFromBandwidth", () => {
58
61
  it("should return STANDARD_FEES_NATIVE if bandwidth is insufficient", async () => {
59
- (getAccount as jest.Mock).mockResolvedValue([{ address: "mock-contract-address" }]);
60
- (extractBandwidthInfo as jest.Mock).mockReturnValue({
62
+ mockGetAccount.mockResolvedValue([{ address: "mock-contract-address", trc20: [] }]);
63
+ mockExtractBandwidthInfo.mockReturnValue({
61
64
  freeUsed: new BigNumber(0),
62
65
  freeLimit: new BigNumber(0),
63
66
  gainedUsed: new BigNumber(0),
@@ -69,8 +72,8 @@ describe("getEstimatedFees", () => {
69
72
  });
70
73
 
71
74
  it("should return 0 if bandwidth is sufficient", async () => {
72
- (getAccount as jest.Mock).mockResolvedValue([{ address: "mock-contract-address" }]);
73
- (extractBandwidthInfo as jest.Mock).mockReturnValue({
75
+ mockGetAccount.mockResolvedValue([{ address: "mock-contract-address", trc20: [] }]);
76
+ mockExtractBandwidthInfo.mockReturnValue({
74
77
  freeUsed: new BigNumber(0),
75
78
  freeLimit: new BigNumber(500),
76
79
  gainedUsed: new BigNumber(0),
@@ -84,8 +87,10 @@ describe("getEstimatedFees", () => {
84
87
 
85
88
  describe("getFeesFromAccountActivation", () => {
86
89
  it("should return ACTIVATION_FEES if recipient account is not active", async () => {
87
- (getAccount as jest.Mock).mockResolvedValue([]);
88
- (extractBandwidthInfo as jest.Mock).mockReturnValue({
90
+ mockGetAccount.mockResolvedValue([]);
91
+ mockExtractBandwidthInfo.mockReturnValue({
92
+ freeUsed: new BigNumber(0),
93
+ freeLimit: new BigNumber(0),
89
94
  gainedUsed: new BigNumber(0),
90
95
  gainedLimit: new BigNumber(0),
91
96
  });
@@ -95,8 +100,10 @@ describe("getEstimatedFees", () => {
95
100
  });
96
101
 
97
102
  it("should return STANDARD_FEES_TRC_20 if recipient has TRC20 balance", async () => {
98
- (getAccount as jest.Mock).mockResolvedValue([{ address: "mock-contract-address" }]);
99
- (extractBandwidthInfo as jest.Mock).mockReturnValue({
103
+ mockGetAccount.mockResolvedValue([{ address: "mock-contract-address", trc20: [] }]);
104
+ mockExtractBandwidthInfo.mockReturnValue({
105
+ freeUsed: new BigNumber(0),
106
+ freeLimit: new BigNumber(0),
100
107
  gainedUsed: new BigNumber(0),
101
108
  gainedLimit: new BigNumber(500),
102
109
  });
@@ -106,9 +113,11 @@ describe("getEstimatedFees", () => {
106
113
  });
107
114
 
108
115
  it("should return estimated fees for TRC20 token transfer", async () => {
109
- (getAccount as jest.Mock).mockResolvedValue([]);
110
- (estimateFees as jest.Mock).mockResolvedValue(new BigNumber(1000));
111
- (extractBandwidthInfo as jest.Mock).mockReturnValue({
116
+ mockGetAccount.mockResolvedValue([]);
117
+ mockEstimateFees.mockResolvedValue(1000n);
118
+ mockExtractBandwidthInfo.mockReturnValue({
119
+ freeUsed: new BigNumber(0),
120
+ freeLimit: new BigNumber(0),
112
121
  gainedUsed: new BigNumber(0),
113
122
  gainedLimit: new BigNumber(0),
114
123
  });
@@ -120,8 +129,8 @@ describe("getEstimatedFees", () => {
120
129
 
121
130
  describe("getEstimatedFees", () => {
122
131
  it("should prioritize account activation fees over bandwidth fees", async () => {
123
- (getAccount as jest.Mock).mockResolvedValue([]);
124
- (extractBandwidthInfo as jest.Mock).mockReturnValue({
132
+ mockGetAccount.mockResolvedValue([]);
133
+ mockExtractBandwidthInfo.mockReturnValue({
125
134
  freeUsed: new BigNumber(0),
126
135
  freeLimit: new BigNumber(0),
127
136
  gainedUsed: new BigNumber(0),
@@ -133,8 +142,8 @@ describe("getEstimatedFees", () => {
133
142
  });
134
143
 
135
144
  it("should return bandwidth fees if no account activation is required", async () => {
136
- (getAccount as jest.Mock).mockResolvedValue([{ trc20: [] }]);
137
- (extractBandwidthInfo as jest.Mock).mockReturnValue({
145
+ mockGetAccount.mockResolvedValue([{ address: "mock-address", trc20: [] }]);
146
+ mockExtractBandwidthInfo.mockReturnValue({
138
147
  freeUsed: new BigNumber(0),
139
148
  freeLimit: new BigNumber(500),
140
149
  gainedUsed: new BigNumber(0),
@@ -1,4 +1,4 @@
1
- import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/index";
1
+ import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets";
2
2
  import { Account, AccountBridge, SyncConfig, TransactionCommon } from "@ledgerhq/types-live";
3
3
  import BigNumber from "bignumber.js";
4
4
  import { firstValueFrom, reduce } from "rxjs";
@@ -1,5 +1,5 @@
1
1
  import { makeScanAccounts } from "@ledgerhq/coin-framework/bridge/jsHelpers";
2
- import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/index";
2
+ import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets";
3
3
  import { Account, AccountBridge, SyncConfig, TransactionCommon } from "@ledgerhq/types-live";
4
4
  import BigNumber from "bignumber.js";
5
5
  import { firstValueFrom, reduce } from "rxjs";
@@ -12,6 +12,9 @@ import { createBridges } from "./index";
12
12
  import { getAccountShape } from "./synchronization";
13
13
  import { setupServer } from "msw/node";
14
14
  import { AccountTronAPI } from "../network/types";
15
+ import { getCryptoAssetsStore } from "@ledgerhq/coin-framework/crypto-assets/index";
16
+
17
+ jest.mock("@ledgerhq/coin-framework/crypto-assets/index");
15
18
 
16
19
  const currency = getCryptoCurrencyById("tron");
17
20
  const defaultSyncConfig = {
@@ -115,6 +118,88 @@ describe("sync", () => {
115
118
  let bridge: ReturnType<typeof createBridges>;
116
119
 
117
120
  beforeAll(() => {
121
+ // Mock the crypto assets store with the expected tokens
122
+ const parentCurrency = {
123
+ type: "CryptoCurrency",
124
+ id: "tron",
125
+ coinType: 195,
126
+ name: "Tron",
127
+ managerAppName: "Tron",
128
+ ticker: "TRX",
129
+ scheme: "tron",
130
+ color: "#D9012C",
131
+ family: "tron",
132
+ blockAvgTime: 9,
133
+ units: [{ name: "TRX", code: "TRX", magnitude: 6 }],
134
+ explorerViews: [
135
+ {
136
+ tx: "https://tronscan.org/#/transaction/$hash",
137
+ address: "https://tronscan.org/#/address/$address",
138
+ },
139
+ ],
140
+ keywords: ["trx", "tron"],
141
+ };
142
+
143
+ const mockTokens = {
144
+ "tron/trc10/1002000": {
145
+ type: "TokenCurrency",
146
+ id: "tron/trc10/1002000",
147
+ contractAddress: "TF5Bn4cJCT6GVeUgyCN4rBhDg42KBrpAjg",
148
+ parentCurrency,
149
+ tokenType: "trc10",
150
+ name: "BitTorrent Old",
151
+ ticker: "BTTOLD",
152
+ delisted: true,
153
+ disableCountervalue: false,
154
+ ledgerSignature:
155
+ "0a0a426974546f7272656e7410061a46304402202e2502f36b00e57be785fc79ec4043abcdd4fdd1b58d737ce123599dffad2cb602201702c307f009d014a553503b499591558b3634ceee4c054c61cedd8aca94c02b",
156
+ units: [{ name: "BitTorrent Old", code: "BTTOLD", magnitude: 6 }],
157
+ },
158
+ "tron/trc20/tla2f6vpqdgre67v1736s7bj8ray5wyju7": {
159
+ type: "TokenCurrency",
160
+ id: "tron/trc20/tla2f6vpqdgre67v1736s7bj8ray5wyju7",
161
+ contractAddress: "TLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7",
162
+ parentCurrency,
163
+ tokenType: "trc20",
164
+ name: "WINK",
165
+ ticker: "WIN",
166
+ delisted: false,
167
+ disableCountervalue: false,
168
+ ledgerSignature: null,
169
+ units: [{ name: "WINK", code: "WIN", magnitude: 6 }],
170
+ },
171
+ "tron/trc20/tcfll5dx5zjdknwuesxxi1vpwjlvmwzzy9": {
172
+ type: "TokenCurrency",
173
+ id: "tron/trc20/tcfll5dx5zjdknwuesxxi1vpwjlvmwzzy9",
174
+ contractAddress: "TCFLL5dx5ZJdKnWuesXxi1VPwjLVmWZZy9",
175
+ parentCurrency,
176
+ tokenType: "trc20",
177
+ name: "JUST GOV",
178
+ ticker: "JST",
179
+ delisted: false,
180
+ disableCountervalue: false,
181
+ ledgerSignature: null,
182
+ units: [{ name: "JUST GOV", code: "JST", magnitude: 18 }],
183
+ },
184
+ };
185
+
186
+ (getCryptoAssetsStore as jest.Mock).mockReturnValue({
187
+ findTokenById: jest.fn().mockImplementation((id: string) => {
188
+ return Promise.resolve(mockTokens[id as keyof typeof mockTokens] || null);
189
+ }),
190
+ findTokenByAddressInCurrency: jest
191
+ .fn()
192
+ .mockImplementation((address: string, currencyId: string) => {
193
+ if (currencyId === "tron") {
194
+ const token = Object.values(mockTokens).find(
195
+ token => token.contractAddress.toLowerCase() === address.toLowerCase(),
196
+ );
197
+ return Promise.resolve(token || null);
198
+ }
199
+ return Promise.resolve(null);
200
+ }),
201
+ });
202
+
118
203
  const signer = jest.fn();
119
204
  const coinConfig = (): TronCoinConfig => ({
120
205
  status: {
@@ -163,6 +248,12 @@ describe("scanAccounts", () => {
163
248
  beforeAll(() => {
164
249
  spyGetTronAccountNetwork = jest.spyOn(tronNetwork, "getTronAccountNetwork");
165
250
 
251
+ // Mock the crypto assets store for scanAccounts tests - same as sync tests
252
+ (getCryptoAssetsStore as jest.Mock).mockReturnValue({
253
+ findTokenById: jest.fn().mockResolvedValue(null),
254
+ findTokenByAddressInCurrency: jest.fn().mockResolvedValue(null),
255
+ });
256
+
166
257
  coinConfig.setCoinConfig(() => ({
167
258
  status: {
168
259
  type: "active",
@@ -5,7 +5,7 @@ import {
5
5
  } from "@ledgerhq/coin-framework/account";
6
6
  import { GetAccountShape, makeSync } from "@ledgerhq/coin-framework/bridge/jsHelpers";
7
7
  import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
8
- import { findTokenByAddressInCurrency, findTokenById } from "@ledgerhq/cryptoassets/index";
8
+ import { getCryptoAssetsStore } from "@ledgerhq/coin-framework/crypto-assets/index";
9
9
  import { Account, TokenAccount } from "@ledgerhq/types-live";
10
10
  import BigNumber from "bignumber.js";
11
11
  import compact from "lodash/compact";
@@ -22,6 +22,7 @@ import {
22
22
  isAccountEmpty,
23
23
  } from "./utils";
24
24
  import { getAccount } from "../logic/getAccount";
25
+ import { AccountTronAPI } from "../network/types";
25
26
 
26
27
  type TronToken = {
27
28
  key: string;
@@ -32,6 +33,43 @@ type TronToken = {
32
33
 
33
34
  // the balance does not update straightaway so we should ignore recent operations if they are in pending for a bit
34
35
  const PREFER_PENDING_OPERATIONS_UNTIL_BLOCK_VALIDATION = 35;
36
+ const MAX_OPERATIONS_PAGE_SIZE = 1000;
37
+
38
+ async function getTrc10Tokens(acc: AccountTronAPI): Promise<TronToken[]> {
39
+ const trc10Tokens: TronToken[] = [];
40
+ for (const { key, value } of get(acc, "assetV2", []) as { key: string; value: number }[]) {
41
+ const tokenInfo = await getCryptoAssetsStore().findTokenById(`tron/trc10/${key}`);
42
+ if (tokenInfo) {
43
+ trc10Tokens.push({
44
+ key,
45
+ type: "trc10",
46
+ tokenId: tokenInfo.id,
47
+ balance: value.toString(),
48
+ });
49
+ }
50
+ }
51
+ return trc10Tokens;
52
+ }
53
+
54
+ async function getTrc20Tokens(acc: AccountTronAPI, currencyId: string): Promise<TronToken[]> {
55
+ const trc20Tokens: TronToken[] = [];
56
+ for (const trc20 of get(acc, "trc20", []) as Record<string, string>[]) {
57
+ const [[contractAddress, balance]] = Object.entries(trc20);
58
+ const tokenInfo = await getCryptoAssetsStore().findTokenByAddressInCurrency(
59
+ contractAddress,
60
+ currencyId,
61
+ );
62
+ if (tokenInfo) {
63
+ trc20Tokens.push({
64
+ key: contractAddress,
65
+ type: "trc20",
66
+ tokenId: tokenInfo.id,
67
+ balance,
68
+ });
69
+ }
70
+ }
71
+ return trc20Tokens;
72
+ }
35
73
 
36
74
  export const getAccountShape: GetAccountShape<TronAccount> = async (
37
75
  { initialAccount, currency, address, derivationMode },
@@ -58,14 +96,10 @@ export const getAccountShape: GetAccountShape<TronAccount> = async (
58
96
  }
59
97
 
60
98
  const acc = tronAcc[0];
61
- const cacheTransactionInfoById = initialAccount
62
- ? {
63
- ...(initialAccount?.tronResources?.cacheTransactionInfoById || {}),
64
- }
65
- : {};
99
+ const cacheTransactionInfoById = initialAccount?.tronResources?.cacheTransactionInfoById || {};
66
100
  const operationsPageSize = Math.min(
67
- 1000,
68
- getOperationsPageSize(initialAccount && initialAccount.id, syncConfig),
101
+ MAX_OPERATIONS_PAGE_SIZE,
102
+ getOperationsPageSize(initialAccount?.id, syncConfig),
69
103
  );
70
104
  // FIXME: this is not optional especially that we might already have initialAccount
71
105
  // use minimalOperationsBuilderSync to reconciliate and KEEP REF
@@ -86,68 +120,36 @@ export const getAccountShape: GetAccountShape<TronAccount> = async (
86
120
  parentTxs.map(tx => txInfoToOperation(accountId, address, tx)),
87
121
  );
88
122
 
89
- const trc10Tokens = get(acc, "assetV2", []).reduce(
90
- (accumulator: TronToken[], { key, value }: { key: string; value: number }) => {
91
- const tokenInfo = findTokenById(`tron/trc10/${key}`);
92
- if (tokenInfo) {
93
- accumulator.push({
94
- key,
95
- type: "trc10",
96
- tokenId: tokenInfo.id,
97
- balance: value.toString(),
98
- });
99
- }
100
- return accumulator;
101
- },
102
- [],
103
- );
104
-
105
- const trc20Tokens = get(acc, "trc20", []).reduce(
106
- (accumulator: TronToken[], trc20: Record<string, string>) => {
107
- const [[contractAddress, balance]] = Object.entries(trc20);
108
- const tokenInfo = findTokenByAddressInCurrency(contractAddress, currency.id);
109
- if (tokenInfo) {
110
- accumulator.push({
111
- key: contractAddress,
112
- type: "trc20",
113
- tokenId: tokenInfo.id,
114
- balance,
115
- });
116
- }
117
- return accumulator;
118
- },
119
- [],
120
- );
123
+ const trc10Tokens = await getTrc10Tokens(acc);
124
+ const trc20Tokens = await getTrc20Tokens(acc, currency.id);
121
125
 
122
126
  const { blacklistedTokenIds = [] } = syncConfig;
123
127
 
124
- const subAccounts: TokenAccount[] = compact(
125
- trc10Tokens.concat(trc20Tokens).map(({ key, tokenId, balance }: TronToken) => {
126
- const { blacklistedTokenIds = [] } = syncConfig;
127
- const token = findTokenById(tokenId);
128
- if (!token || blacklistedTokenIds.includes(tokenId)) return;
129
- const id = encodeTokenAccountId(accountId, token);
130
- const tokenTxs = txs.filter(tx => tx.tokenId === key);
131
- const operations = compact(tokenTxs.map(tx => txInfoToOperation(id, address, tx)));
132
- const maybeExistingSubAccount = initialAccount?.subAccounts?.find(a => a.id === id);
133
- const bnBalance = new BigNumber(balance);
134
- const sub: TokenAccount = {
135
- type: "TokenAccount",
136
- id,
137
- parentId: accountId,
138
- token,
139
- balance: bnBalance,
140
- spendableBalance: bnBalance,
141
- operationsCount: operations.length,
142
- operations,
143
- pendingOperations: maybeExistingSubAccount ? maybeExistingSubAccount.pendingOperations : [],
144
- creationDate: operations.length > 0 ? operations[operations.length - 1].date : new Date(),
145
- swapHistory: maybeExistingSubAccount ? maybeExistingSubAccount.swapHistory : [],
146
- balanceHistoryCache: emptyHistoryCache, // calculated in the jsHelpers
147
- };
148
- return sub;
149
- }),
150
- );
128
+ const subAccounts: TokenAccount[] = [];
129
+ for (const { key, tokenId, balance } of trc10Tokens.concat(trc20Tokens)) {
130
+ const token = await getCryptoAssetsStore().findTokenById(tokenId);
131
+ if (!token || blacklistedTokenIds.includes(tokenId)) continue;
132
+ const id = encodeTokenAccountId(accountId, token);
133
+ const tokenTxs = txs.filter(tx => tx.tokenId === key);
134
+ const operations = compact(tokenTxs.map(tx => txInfoToOperation(id, address, tx)));
135
+ const maybeExistingSubAccount = initialAccount?.subAccounts?.find(a => a.id === id);
136
+ const bnBalance = new BigNumber(balance);
137
+ const sub: TokenAccount = {
138
+ type: "TokenAccount",
139
+ id,
140
+ parentId: accountId,
141
+ token,
142
+ balance: bnBalance,
143
+ spendableBalance: bnBalance,
144
+ operationsCount: operations.length,
145
+ operations,
146
+ pendingOperations: maybeExistingSubAccount ? maybeExistingSubAccount.pendingOperations : [],
147
+ creationDate: operations.length > 0 ? operations[operations.length - 1].date : new Date(),
148
+ swapHistory: maybeExistingSubAccount ? maybeExistingSubAccount.swapHistory : [],
149
+ balanceHistoryCache: emptyHistoryCache, // calculated in the jsHelpers
150
+ };
151
+ subAccounts.push(sub);
152
+ }
151
153
 
152
154
  // Filter blacklisted tokens from the initial account's subAccounts
153
155
  // Could be use to filter out tokens that got their CAL id changed
@@ -231,19 +233,12 @@ export const postSync = (initial: TronAccount, parent: TronAccount): TronAccount
231
233
  const mergeSubAccounts = (subAccounts1: TokenAccount[], subAccounts2: TokenAccount[]) => {
232
234
  const existingIds = new Set(subAccounts1.map(subAccount => subAccount.id));
233
235
  const filteredSubAccounts2: TokenAccount[] = subAccounts2
234
- .map(subAccount => {
235
- if (existingIds.has(subAccount.id)) {
236
- return null;
237
- } else {
238
- // Set balance and spendableBalance to 0 has if they are not here it means balance is 0
239
- return {
240
- ...subAccount,
241
- balance: new BigNumber(0),
242
- spendableBalance: new BigNumber(0),
243
- };
244
- }
245
- })
246
- .filter((elt): elt is NonNullable<typeof elt> => elt !== null);
236
+ .filter(subAccount => !existingIds.has(subAccount.id))
237
+ .map(subAccount => ({
238
+ ...subAccount,
239
+ balance: new BigNumber(0),
240
+ spendableBalance: new BigNumber(0),
241
+ }));
247
242
 
248
243
  return subAccounts1.concat(filteredSubAccounts2);
249
244
  };
@@ -2,7 +2,7 @@ import { BigNumber } from "bignumber.js";
2
2
  import invariant from "invariant";
3
3
  import expect from "expect";
4
4
  import type { Transaction } from "../types";
5
- import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/index";
5
+ import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets";
6
6
  import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies";
7
7
  import { botTest, pickSiblings } from "@ledgerhq/coin-framework/bot/specs";
8
8
  import type { AppSpec, TransactionDestinationTestInput } from "@ledgerhq/coin-framework/bot/types";