@ledgerhq/coin-framework 0.3.5 → 0.3.6-next.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.
- package/.eslintrc.js +8 -46
- package/CHANGELOG.md +13 -0
- package/package.json +9 -18
- package/src/account/accountId.ts +20 -30
- package/src/account/accountName.ts +1 -4
- package/src/account/balanceHistoryCache.ts +18 -37
- package/src/account/groupOperations.ts +4 -6
- package/src/account/helpers.test.ts +6 -26
- package/src/account/helpers.ts +33 -66
- package/src/account/ordering.ts +10 -18
- package/src/account/pending.ts +6 -15
- package/src/account/serialization.ts +8 -19
- package/src/account/support.ts +8 -18
- package/src/account.test.ts +25 -40
- package/src/bot/specs.ts +24 -43
- package/src/bot/types.ts +1 -1
- package/src/bridge/getAddressWrapper.ts +5 -13
- package/src/bridge/jsHelpers.ts +215 -259
- package/src/cache.ts +4 -4
- package/src/currencies/BigNumberToLocaleString.test.ts +25 -33
- package/src/currencies/BigNumberToLocaleString.ts +3 -8
- package/src/currencies/CurrencyURIScheme.ts +1 -3
- package/src/currencies/chopCurrencyUnitDecimals.ts +1 -4
- package/src/currencies/formatCurrencyUnit.ts +11 -21
- package/src/currencies/index.ts +1 -4
- package/src/currencies/localeUtility.ts +2 -4
- package/src/currencies/parseCurrencyUnit.ts +1 -4
- package/src/currencies/sanitizeValueString.ts +1 -1
- package/src/currencies/support.ts +3 -9
- package/src/derivation.test.ts +1 -4
- package/src/derivation.ts +45 -95
- package/src/errors.test.ts +1 -1
- package/src/errors.ts +2 -6
- package/src/mocks/account.ts +27 -80
- package/src/mocks/fixtures/nfts.test.ts +2 -6
- package/src/mocks/fixtures/nfts.ts +12 -38
- package/src/mocks/helpers.ts +2 -8
- package/src/nft/nftId.ts +2 -2
- package/src/operation.test.ts +6 -31
- package/src/operation.ts +12 -26
- package/src/transaction/common.ts +9 -22
package/src/account/ordering.ts
CHANGED
|
@@ -2,10 +2,7 @@ import { BigNumber } from "bignumber.js";
|
|
|
2
2
|
import { flattenAccounts, getAccountCurrency } from "./helpers";
|
|
3
3
|
import type { FlattenAccountsOptions } from "./helpers";
|
|
4
4
|
import type { Account, AccountLike } from "@ledgerhq/types-live";
|
|
5
|
-
import type {
|
|
6
|
-
CryptoCurrency,
|
|
7
|
-
TokenCurrency,
|
|
8
|
-
} from "@ledgerhq/types-cryptoassets";
|
|
5
|
+
import type { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets";
|
|
9
6
|
|
|
10
7
|
export type AccountComparator = (a: AccountLike, b: AccountLike) => number;
|
|
11
8
|
|
|
@@ -29,8 +26,8 @@ export const sortAccountsComparatorFromOrder = (
|
|
|
29
26
|
orderAccounts: string,
|
|
30
27
|
calculateCountervalue: (
|
|
31
28
|
currency: TokenCurrency | CryptoCurrency,
|
|
32
|
-
value: BigNumber
|
|
33
|
-
) => BigNumber | null | undefined
|
|
29
|
+
value: BigNumber,
|
|
30
|
+
) => BigNumber | null | undefined,
|
|
34
31
|
): AccountComparator => {
|
|
35
32
|
const [order, sort] = orderAccounts.split("|");
|
|
36
33
|
const ascValue = sort === "desc" ? -1 : 1;
|
|
@@ -47,9 +44,7 @@ export const sortAccountsComparatorFromOrder = (
|
|
|
47
44
|
|
|
48
45
|
const lazyCalcCV = (a: AccountLike) => {
|
|
49
46
|
if (a.id in cvCaches) return cvCaches[a.id];
|
|
50
|
-
const v =
|
|
51
|
-
calculateCountervalue(getAccountCurrency(a), a.balance) ||
|
|
52
|
-
new BigNumber(-1);
|
|
47
|
+
const v = calculateCountervalue(getAccountCurrency(a), a.balance) || new BigNumber(-1);
|
|
53
48
|
cvCaches[a.id] = v;
|
|
54
49
|
return v;
|
|
55
50
|
};
|
|
@@ -62,7 +57,7 @@ export const sortAccountsComparatorFromOrder = (
|
|
|
62
57
|
};
|
|
63
58
|
export const comparatorSortAccounts = <TA extends AccountLike>(
|
|
64
59
|
accounts: TA[],
|
|
65
|
-
comparator: AccountComparator
|
|
60
|
+
comparator: AccountComparator,
|
|
66
61
|
): TA[] => {
|
|
67
62
|
const meta = accounts
|
|
68
63
|
.map((ta, index) => ({
|
|
@@ -77,24 +72,24 @@ export const comparatorSortAccounts = <TA extends AccountLike>(
|
|
|
77
72
|
}
|
|
78
73
|
|
|
79
74
|
// otherwise, need to reorder
|
|
80
|
-
return meta.map(
|
|
75
|
+
return meta.map(m => accounts[m.index]);
|
|
81
76
|
};
|
|
82
77
|
// flatten accounts and sort between them (used for grid mode)
|
|
83
78
|
export const flattenSortAccounts = (
|
|
84
79
|
accounts: Account[],
|
|
85
80
|
comparator: AccountComparator,
|
|
86
|
-
o?: FlattenAccountsOptions
|
|
81
|
+
o?: FlattenAccountsOptions,
|
|
87
82
|
): AccountLike[] => {
|
|
88
83
|
return comparatorSortAccounts(flattenAccounts(accounts, o), comparator);
|
|
89
84
|
};
|
|
90
85
|
// sort top level accounts and the inner sub accounts if necessary (used for lists)
|
|
91
86
|
export const nestedSortAccounts = (
|
|
92
87
|
topAccounts: Account[],
|
|
93
|
-
comparator: AccountComparator
|
|
88
|
+
comparator: AccountComparator,
|
|
94
89
|
): Account[] => {
|
|
95
90
|
let oneAccountHaveChanged = false;
|
|
96
91
|
// first of all we sort the inner token accounts
|
|
97
|
-
const accounts = topAccounts.map(
|
|
92
|
+
const accounts = topAccounts.map(a => {
|
|
98
93
|
if (!a.subAccounts) return a;
|
|
99
94
|
const subAccounts = comparatorSortAccounts(a.subAccounts, comparator);
|
|
100
95
|
if (subAccounts === a.subAccounts) return a;
|
|
@@ -102,8 +97,5 @@ export const nestedSortAccounts = (
|
|
|
102
97
|
return { ...a, subAccounts };
|
|
103
98
|
});
|
|
104
99
|
// then we sort again between them
|
|
105
|
-
return comparatorSortAccounts(
|
|
106
|
-
oneAccountHaveChanged ? accounts : topAccounts,
|
|
107
|
-
comparator
|
|
108
|
-
);
|
|
100
|
+
return comparatorSortAccounts(oneAccountHaveChanged ? accounts : topAccounts, comparator);
|
|
109
101
|
};
|
package/src/account/pending.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import type { Account, Operation, SubAccount } from "@ledgerhq/types-live";
|
|
2
2
|
import { getEnv } from "@ledgerhq/live-env";
|
|
3
|
-
export function shouldRetainPendingOperation(
|
|
4
|
-
account: Account,
|
|
5
|
-
op: Operation
|
|
6
|
-
): boolean {
|
|
3
|
+
export function shouldRetainPendingOperation(account: Account, op: Operation): boolean {
|
|
7
4
|
// FIXME: valueOf to compare dates in typescript
|
|
8
5
|
const delay = new Date().valueOf() - op.date.valueOf();
|
|
9
6
|
const last = account.operations[0];
|
|
@@ -22,22 +19,19 @@ export function shouldRetainPendingOperation(
|
|
|
22
19
|
|
|
23
20
|
const appendPendingOp = (ops: Operation[], op: Operation) => {
|
|
24
21
|
const filtered: Operation[] = ops.filter(
|
|
25
|
-
|
|
22
|
+
o => o.transactionSequenceNumber !== op.transactionSequenceNumber,
|
|
26
23
|
);
|
|
27
24
|
filtered.unshift(op);
|
|
28
25
|
return filtered;
|
|
29
26
|
};
|
|
30
27
|
|
|
31
|
-
export const addPendingOperation = (
|
|
32
|
-
account: Account,
|
|
33
|
-
operation: Operation
|
|
34
|
-
): Account => {
|
|
28
|
+
export const addPendingOperation = (account: Account, operation: Operation): Account => {
|
|
35
29
|
const accountCopy = { ...account };
|
|
36
30
|
const { subOperations } = operation;
|
|
37
31
|
const { subAccounts } = account;
|
|
38
32
|
|
|
39
33
|
function addInSubAccount(subaccounts: SubAccount[], op: Operation) {
|
|
40
|
-
const acc = subaccounts.find(
|
|
34
|
+
const acc = subaccounts.find(sub => sub.id === op.accountId);
|
|
41
35
|
|
|
42
36
|
if (acc) {
|
|
43
37
|
const copy: SubAccount = { ...acc };
|
|
@@ -48,17 +42,14 @@ export const addPendingOperation = (
|
|
|
48
42
|
|
|
49
43
|
if (subOperations && subAccounts) {
|
|
50
44
|
const taCopy: SubAccount[] = subAccounts.slice(0);
|
|
51
|
-
subOperations.forEach(
|
|
45
|
+
subOperations.forEach(op => {
|
|
52
46
|
addInSubAccount(taCopy, op);
|
|
53
47
|
});
|
|
54
48
|
accountCopy.subAccounts = taCopy;
|
|
55
49
|
}
|
|
56
50
|
|
|
57
51
|
if (accountCopy.id === operation.accountId) {
|
|
58
|
-
accountCopy.pendingOperations = appendPendingOp(
|
|
59
|
-
accountCopy.pendingOperations,
|
|
60
|
-
operation
|
|
61
|
-
);
|
|
52
|
+
accountCopy.pendingOperations = appendPendingOp(accountCopy.pendingOperations, operation);
|
|
62
53
|
} else if (subAccounts) {
|
|
63
54
|
const taCopy: SubAccount[] = subAccounts.slice(0);
|
|
64
55
|
addInSubAccount(taCopy, operation);
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { BigNumber } from "bignumber.js";
|
|
2
2
|
import type { Operation, OperationRaw, SubAccount } from "@ledgerhq/types-live";
|
|
3
3
|
|
|
4
|
-
export type ExtractExtraFn = (
|
|
5
|
-
extra: Record<string, any>
|
|
6
|
-
) => Record<string, any>;
|
|
4
|
+
export type ExtractExtraFn = (extra: Record<string, any>) => Record<string, any>;
|
|
7
5
|
|
|
8
6
|
export const toOperationRaw = (
|
|
9
7
|
{
|
|
@@ -30,7 +28,7 @@ export const toOperationRaw = (
|
|
|
30
28
|
tokenId,
|
|
31
29
|
transactionRaw,
|
|
32
30
|
}: Operation,
|
|
33
|
-
preserveSubOperation?: boolean
|
|
31
|
+
preserveSubOperation?: boolean,
|
|
34
32
|
): OperationRaw => {
|
|
35
33
|
const copy: OperationRaw = {
|
|
36
34
|
id,
|
|
@@ -64,9 +62,7 @@ export const toOperationRaw = (
|
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
if (internalOperations) {
|
|
67
|
-
copy.internalOperations = internalOperations.map((o: Operation) =>
|
|
68
|
-
toOperationRaw(o)
|
|
69
|
-
);
|
|
65
|
+
copy.internalOperations = internalOperations.map((o: Operation) => toOperationRaw(o));
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
if (nftOperations) {
|
|
@@ -79,10 +75,7 @@ export const toOperationRaw = (
|
|
|
79
75
|
|
|
80
76
|
return copy;
|
|
81
77
|
};
|
|
82
|
-
export const inferSubOperations = (
|
|
83
|
-
txHash: string,
|
|
84
|
-
subAccounts: SubAccount[]
|
|
85
|
-
): Operation[] => {
|
|
78
|
+
export const inferSubOperations = (txHash: string, subAccounts: SubAccount[]): Operation[] => {
|
|
86
79
|
const all: Operation[] = [];
|
|
87
80
|
|
|
88
81
|
for (let i = 0; i < subAccounts.length; i++) {
|
|
@@ -132,7 +125,7 @@ export const fromOperationRaw = (
|
|
|
132
125
|
transactionRaw,
|
|
133
126
|
}: OperationRaw,
|
|
134
127
|
accountId: string,
|
|
135
|
-
subAccounts?: SubAccount[] | null | undefined
|
|
128
|
+
subAccounts?: SubAccount[] | null | undefined,
|
|
136
129
|
): Operation => {
|
|
137
130
|
const res: Operation = {
|
|
138
131
|
id,
|
|
@@ -164,21 +157,17 @@ export const fromOperationRaw = (
|
|
|
164
157
|
if (subAccounts) {
|
|
165
158
|
res.subOperations = inferSubOperations(hash, subAccounts);
|
|
166
159
|
} else if (subOperations) {
|
|
167
|
-
res.subOperations = subOperations.map((o: OperationRaw) =>
|
|
168
|
-
fromOperationRaw(o, o.accountId)
|
|
169
|
-
);
|
|
160
|
+
res.subOperations = subOperations.map((o: OperationRaw) => fromOperationRaw(o, o.accountId));
|
|
170
161
|
}
|
|
171
162
|
|
|
172
163
|
if (internalOperations) {
|
|
173
164
|
res.internalOperations = internalOperations.map((o: OperationRaw) =>
|
|
174
|
-
fromOperationRaw(o, o.accountId)
|
|
165
|
+
fromOperationRaw(o, o.accountId),
|
|
175
166
|
);
|
|
176
167
|
}
|
|
177
168
|
|
|
178
169
|
if (nftOperations) {
|
|
179
|
-
res.nftOperations = nftOperations.map((o: OperationRaw) =>
|
|
180
|
-
fromOperationRaw(o, o.accountId)
|
|
181
|
-
);
|
|
170
|
+
res.nftOperations = nftOperations.map((o: OperationRaw) => fromOperationRaw(o, o.accountId));
|
|
182
171
|
}
|
|
183
172
|
|
|
184
173
|
if (transactionRaw !== undefined) {
|
package/src/account/support.ts
CHANGED
|
@@ -4,10 +4,7 @@ import {
|
|
|
4
4
|
UnavailableTezosOriginatedAccountReceive,
|
|
5
5
|
} from "@ledgerhq/errors";
|
|
6
6
|
import { getEnv } from "@ledgerhq/live-env";
|
|
7
|
-
import {
|
|
8
|
-
getAllDerivationModes,
|
|
9
|
-
getDerivationModesForCurrency,
|
|
10
|
-
} from "../derivation";
|
|
7
|
+
import { getAllDerivationModes, getDerivationModesForCurrency } from "../derivation";
|
|
11
8
|
import { isCurrencySupported } from "../currencies";
|
|
12
9
|
import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
|
|
13
10
|
import type { Account, AccountLike } from "@ledgerhq/types-live";
|
|
@@ -15,7 +12,7 @@ import type { DerivationMode } from "../derivation";
|
|
|
15
12
|
|
|
16
13
|
export const shouldShowNewAccount = (
|
|
17
14
|
currency: CryptoCurrency,
|
|
18
|
-
derivationMode: DerivationMode
|
|
15
|
+
derivationMode: DerivationMode,
|
|
19
16
|
): boolean => {
|
|
20
17
|
const modes = getDerivationModesForCurrency(currency);
|
|
21
18
|
// last mode is always creatable by convention
|
|
@@ -37,25 +34,18 @@ export const shouldShowNewAccount = (
|
|
|
37
34
|
};
|
|
38
35
|
export const getReceiveFlowError = (
|
|
39
36
|
account: AccountLike,
|
|
40
|
-
parentAccount: Account | null | undefined
|
|
37
|
+
parentAccount: Account | null | undefined,
|
|
41
38
|
): Error | null | undefined => {
|
|
42
39
|
if (parentAccount && parentAccount.currency.id === "tezos") {
|
|
43
40
|
return new UnavailableTezosOriginatedAccountReceive("");
|
|
44
41
|
}
|
|
45
42
|
};
|
|
46
43
|
|
|
47
|
-
export function checkAccountSupported(
|
|
48
|
-
account
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
) {
|
|
53
|
-
return new AccountNotSupported(
|
|
54
|
-
"derivation not supported " + account.derivationMode,
|
|
55
|
-
{
|
|
56
|
-
reason: account.derivationMode,
|
|
57
|
-
}
|
|
58
|
-
);
|
|
44
|
+
export function checkAccountSupported(account: Account): Error | null | undefined {
|
|
45
|
+
if (!getAllDerivationModes().includes(account.derivationMode as DerivationMode)) {
|
|
46
|
+
return new AccountNotSupported("derivation not supported " + account.derivationMode, {
|
|
47
|
+
reason: account.derivationMode,
|
|
48
|
+
});
|
|
59
49
|
}
|
|
60
50
|
|
|
61
51
|
if (!isCurrencySupported(account.currency)) {
|
package/src/account.test.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import "./test-helpers/staticTime";
|
|
2
2
|
import { BigNumber } from "bignumber.js";
|
|
3
3
|
import flatMap from "lodash/flatMap";
|
|
4
|
-
import {
|
|
5
|
-
getCryptoCurrencyById,
|
|
6
|
-
getTokenById,
|
|
7
|
-
setSupportedCurrencies,
|
|
8
|
-
} from "./currencies";
|
|
4
|
+
import { getCryptoCurrencyById, getTokenById, setSupportedCurrencies } from "./currencies";
|
|
9
5
|
import {
|
|
10
6
|
groupAccountOperationsByDay,
|
|
11
7
|
groupAccountsOperationsByDay,
|
|
@@ -35,10 +31,10 @@ describe("groupAccountOperationsByDay", () => {
|
|
|
35
31
|
expect(res2.completed).toBe(true);
|
|
36
32
|
expect(
|
|
37
33
|
// $FlowFixMe
|
|
38
|
-
flatMap(res2.sections,
|
|
34
|
+
flatMap(res2.sections, s => s.data).slice(0, 10),
|
|
39
35
|
).toMatchObject(
|
|
40
36
|
// $FlowFixMe
|
|
41
|
-
flatMap(res1.sections,
|
|
37
|
+
flatMap(res1.sections, s => s.data),
|
|
42
38
|
);
|
|
43
39
|
});
|
|
44
40
|
test("basic 2", () => {
|
|
@@ -56,10 +52,10 @@ describe("groupAccountOperationsByDay", () => {
|
|
|
56
52
|
expect(res2.completed).toBe(true);
|
|
57
53
|
expect(
|
|
58
54
|
// $FlowFixMe
|
|
59
|
-
flatMap(res2.sections,
|
|
55
|
+
flatMap(res2.sections, s => s.data).slice(0, 100),
|
|
60
56
|
).toMatchObject(
|
|
61
57
|
// $FlowFixMe
|
|
62
|
-
flatMap(res1.sections,
|
|
58
|
+
flatMap(res1.sections, s => s.data),
|
|
63
59
|
);
|
|
64
60
|
});
|
|
65
61
|
test("filterOperation", () => {
|
|
@@ -78,7 +74,7 @@ describe("groupAccountOperationsByDay", () => {
|
|
|
78
74
|
expect(res2).toEqual(
|
|
79
75
|
groupAccountOperationsByDay(account, {
|
|
80
76
|
count: 10,
|
|
81
|
-
})
|
|
77
|
+
}),
|
|
82
78
|
);
|
|
83
79
|
const res3 = groupAccountOperationsByDay(account, {
|
|
84
80
|
count: 10,
|
|
@@ -91,15 +87,13 @@ describe("groupAccountOperationsByDay", () => {
|
|
|
91
87
|
groupAccountOperationsByDay(
|
|
92
88
|
{
|
|
93
89
|
...account,
|
|
94
|
-
operations: account.operations.filter(
|
|
95
|
-
pendingOperations: account.pendingOperations.filter(
|
|
96
|
-
(op) => op.type === "OUT"
|
|
97
|
-
),
|
|
90
|
+
operations: account.operations.filter(op => op.type === "OUT"),
|
|
91
|
+
pendingOperations: account.pendingOperations.filter(op => op.type === "OUT"),
|
|
98
92
|
},
|
|
99
93
|
{
|
|
100
94
|
count: 10,
|
|
101
|
-
}
|
|
102
|
-
)
|
|
95
|
+
},
|
|
96
|
+
),
|
|
103
97
|
);
|
|
104
98
|
});
|
|
105
99
|
test("provide at least the requested count even if some op yield nothing", () => {
|
|
@@ -119,8 +113,7 @@ describe("groupAccountOperationsByDay", () => {
|
|
|
119
113
|
});
|
|
120
114
|
expect(res1.completed).toBe(false);
|
|
121
115
|
expect(
|
|
122
|
-
res1.sections.reduce((acc, s) => acc.concat(s.data), <Operation[]>[])
|
|
123
|
-
.length
|
|
116
|
+
res1.sections.reduce((acc, s) => acc.concat(s.data), <Operation[]>[]).length,
|
|
124
117
|
).toBeGreaterThanOrEqual(100);
|
|
125
118
|
});
|
|
126
119
|
test("to dedup", () => {
|
|
@@ -137,12 +130,12 @@ describe("groupAccountOperationsByDay", () => {
|
|
|
137
130
|
});
|
|
138
131
|
});
|
|
139
132
|
test("shortAddressPreview", () => {
|
|
140
|
-
expect(
|
|
141
|
-
|
|
142
|
-
)
|
|
143
|
-
expect(
|
|
144
|
-
|
|
145
|
-
)
|
|
133
|
+
expect(shortAddressPreview("0x112233445566778899001234567890aAbBcCdDeEfF")).toBe(
|
|
134
|
+
"0x112233...cCdDeEfF",
|
|
135
|
+
);
|
|
136
|
+
expect(shortAddressPreview("0x112233445566778899001234567890aAbBcCdDeEfF", 30)).toBe(
|
|
137
|
+
"0x11223344556...0aAbBcCdDeEfF",
|
|
138
|
+
);
|
|
146
139
|
});
|
|
147
140
|
test("accountWithMandatoryTokens ethereum", () => {
|
|
148
141
|
const currency = getCryptoCurrencyById("ethereum");
|
|
@@ -150,9 +143,7 @@ test("accountWithMandatoryTokens ethereum", () => {
|
|
|
150
143
|
currency,
|
|
151
144
|
subAccountsCount: 5,
|
|
152
145
|
});
|
|
153
|
-
const enhance = accountWithMandatoryTokens(account, [
|
|
154
|
-
getTokenById("ethereum/erc20/0x_project"),
|
|
155
|
-
]);
|
|
146
|
+
const enhance = accountWithMandatoryTokens(account, [getTokenById("ethereum/erc20/0x_project")]);
|
|
156
147
|
const doubleEnhance = accountWithMandatoryTokens(enhance, [
|
|
157
148
|
getTokenById("ethereum/erc20/0x_project"),
|
|
158
149
|
]);
|
|
@@ -161,7 +152,7 @@ test("accountWithMandatoryTokens ethereum", () => {
|
|
|
161
152
|
...account,
|
|
162
153
|
subAccounts: [],
|
|
163
154
|
});
|
|
164
|
-
expect((enhance.subAccounts || []).map(
|
|
155
|
+
expect((enhance.subAccounts || []).map(a => a.id)).toMatchSnapshot();
|
|
165
156
|
});
|
|
166
157
|
test("withoutToken ethereum", () => {
|
|
167
158
|
const isTokenAccount = (account: SubAccount, tokenId: string) =>
|
|
@@ -179,10 +170,7 @@ test("withoutToken ethereum", () => {
|
|
|
179
170
|
subAccountsCount: 0,
|
|
180
171
|
});
|
|
181
172
|
//Enhance the account with some tokens
|
|
182
|
-
const enhance = accountWithMandatoryTokens(
|
|
183
|
-
account,
|
|
184
|
-
tokenIds.map(getTokenById)
|
|
185
|
-
);
|
|
173
|
+
const enhance = accountWithMandatoryTokens(account, tokenIds.map(getTokenById));
|
|
186
174
|
//Get a version of that account without all the tokens
|
|
187
175
|
let demote = enhance;
|
|
188
176
|
|
|
@@ -195,8 +183,8 @@ test("withoutToken ethereum", () => {
|
|
|
195
183
|
|
|
196
184
|
//See if we have added/removed them correctly
|
|
197
185
|
for (const tokenId of tokenIds) {
|
|
198
|
-
expect(saTokens.find(
|
|
199
|
-
expect(saNoTokens.find(
|
|
186
|
+
expect(saTokens.find(a => isTokenAccount(a, tokenId))).toBeTruthy();
|
|
187
|
+
expect(saNoTokens.find(a => isTokenAccount(a, tokenId))).toBeFalsy();
|
|
200
188
|
}
|
|
201
189
|
});
|
|
202
190
|
test("withoutToken tron", () => {
|
|
@@ -215,10 +203,7 @@ test("withoutToken tron", () => {
|
|
|
215
203
|
subAccountsCount: 0,
|
|
216
204
|
});
|
|
217
205
|
//Enhance the account with some tokens
|
|
218
|
-
const enhance = accountWithMandatoryTokens(
|
|
219
|
-
account,
|
|
220
|
-
tokenIds.map(getTokenById)
|
|
221
|
-
);
|
|
206
|
+
const enhance = accountWithMandatoryTokens(account, tokenIds.map(getTokenById));
|
|
222
207
|
//Get a version of that account without all the tokens
|
|
223
208
|
let demote = enhance;
|
|
224
209
|
|
|
@@ -231,7 +216,7 @@ test("withoutToken tron", () => {
|
|
|
231
216
|
|
|
232
217
|
//See if we have added/removed them correctly
|
|
233
218
|
for (const tokenId of tokenIds) {
|
|
234
|
-
expect(saTokens.find(
|
|
235
|
-
expect(saNoTokens.find(
|
|
219
|
+
expect(saTokens.find(a => isTokenAccount(a, tokenId))).toBeTruthy();
|
|
220
|
+
expect(saNoTokens.find(a => isTokenAccount(a, tokenId))).toBeFalsy();
|
|
236
221
|
}
|
|
237
222
|
});
|
package/src/bot/specs.ts
CHANGED
|
@@ -4,11 +4,7 @@ import { log } from "@ledgerhq/logs";
|
|
|
4
4
|
import expect from "expect";
|
|
5
5
|
import sample from "lodash/sample";
|
|
6
6
|
import { isAccountEmpty } from "../account";
|
|
7
|
-
import type {
|
|
8
|
-
DeviceAction,
|
|
9
|
-
DeviceActionArg,
|
|
10
|
-
TransactionDestinationTestInput,
|
|
11
|
-
} from "./types";
|
|
7
|
+
import type { DeviceAction, DeviceActionArg, TransactionDestinationTestInput } from "./types";
|
|
12
8
|
import { Account, TransactionCommon } from "@ledgerhq/types-live";
|
|
13
9
|
import { botTest } from "./bot-test-context";
|
|
14
10
|
import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets";
|
|
@@ -21,16 +17,13 @@ const stepValueTransformDefault = (s: string) => s.trim();
|
|
|
21
17
|
|
|
22
18
|
// TODO should weight the choice to favorize accounts with small amounts
|
|
23
19
|
export function pickSiblings(siblings: Account[], maxAccount = 5): Account {
|
|
24
|
-
const withoutEmpties = siblings.filter(
|
|
20
|
+
const withoutEmpties = siblings.filter(a => a.used);
|
|
25
21
|
|
|
26
22
|
if (withoutEmpties.length >= maxAccount) {
|
|
27
23
|
// we are no longer creating accounts
|
|
28
24
|
const maybeAccount = sample(withoutEmpties);
|
|
29
25
|
if (!maybeAccount) {
|
|
30
|
-
throw new Error(
|
|
31
|
-
"at least one non-empty sibling account exists. maxAccount=" +
|
|
32
|
-
maxAccount
|
|
33
|
-
);
|
|
26
|
+
throw new Error("at least one non-empty sibling account exists. maxAccount=" + maxAccount);
|
|
34
27
|
}
|
|
35
28
|
return maybeAccount;
|
|
36
29
|
}
|
|
@@ -40,14 +33,12 @@ export function pickSiblings(siblings: Account[], maxAccount = 5): Account {
|
|
|
40
33
|
empties.sort((a, b) => a.index - b.index);
|
|
41
34
|
|
|
42
35
|
if (empties.length > 0) {
|
|
43
|
-
empties = empties.filter(
|
|
36
|
+
empties = empties.filter(e => e.index === empties[0].index);
|
|
44
37
|
}
|
|
45
38
|
|
|
46
39
|
const maybeAccount = sample(withoutEmpties.concat(empties));
|
|
47
40
|
if (!maybeAccount) {
|
|
48
|
-
throw new Error(
|
|
49
|
-
"at least one sibling account exists. maxAccount=" + maxAccount
|
|
50
|
-
);
|
|
41
|
+
throw new Error("at least one sibling account exists. maxAccount=" + maxAccount);
|
|
51
42
|
}
|
|
52
43
|
return maybeAccount;
|
|
53
44
|
}
|
|
@@ -77,7 +68,7 @@ type Step<T extends TransactionCommon> = {
|
|
|
77
68
|
acc: Array<{
|
|
78
69
|
title: string;
|
|
79
70
|
value: string;
|
|
80
|
-
}
|
|
71
|
+
}>,
|
|
81
72
|
) => string;
|
|
82
73
|
ignoreAssertionFailure?: boolean;
|
|
83
74
|
trimValue?: boolean;
|
|
@@ -92,7 +83,7 @@ type FlowDesc<T extends TransactionCommon> = {
|
|
|
92
83
|
};
|
|
93
84
|
// generalized logic of device actions
|
|
94
85
|
export function deviceActionFlow<T extends TransactionCommon>(
|
|
95
|
-
description: FlowDesc<T
|
|
86
|
+
description: FlowDesc<T>,
|
|
96
87
|
): DeviceAction<T, State<T>> {
|
|
97
88
|
return (arg: DeviceActionArg<T, State<T>>) => {
|
|
98
89
|
const { transport, event, state, disableStrictStepValueValidation } = arg;
|
|
@@ -111,8 +102,7 @@ export function deviceActionFlow<T extends TransactionCommon>(
|
|
|
111
102
|
// there were accumulated text and we are on new step, we need to release it and compare to expected
|
|
112
103
|
if (currentStep && currentStep.expectedValue) {
|
|
113
104
|
const { expectedValue, ignoreAssertionFailure } = currentStep;
|
|
114
|
-
const stepValueTransform =
|
|
115
|
-
currentStep.stepValueTransform || stepValueTransformDefault;
|
|
105
|
+
const stepValueTransform = currentStep.stepValueTransform || stepValueTransformDefault;
|
|
116
106
|
|
|
117
107
|
if (!ignoreAssertionFailure && !disableStrictStepValueValidation) {
|
|
118
108
|
botTest("deviceAction confirm step '" + stepTitle + "'", () => {
|
|
@@ -122,10 +112,7 @@ export function deviceActionFlow<T extends TransactionCommon>(
|
|
|
122
112
|
// FIXME: OCR of speculos couldn't retrieve S properly
|
|
123
113
|
// Issue on speculos repository : https://github.com/LedgerHQ/speculos/issues/204
|
|
124
114
|
[stepTitle]: expectedValue(arg, acc)
|
|
125
|
-
.replace(
|
|
126
|
-
/S/g,
|
|
127
|
-
arg.appCandidate.model === DeviceModelId.nanoS ? "S" : ""
|
|
128
|
-
)
|
|
115
|
+
.replace(/S/g, arg.appCandidate.model === DeviceModelId.nanoS ? "S" : "")
|
|
129
116
|
.trim(),
|
|
130
117
|
});
|
|
131
118
|
});
|
|
@@ -155,13 +142,12 @@ export function deviceActionFlow<T extends TransactionCommon>(
|
|
|
155
142
|
}
|
|
156
143
|
|
|
157
144
|
if (!finalState) {
|
|
158
|
-
let possibleKnownStep: Step<T> | null | undefined =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
});
|
|
145
|
+
let possibleKnownStep: Step<T> | null | undefined = description.steps.find(s => {
|
|
146
|
+
if (s.maxY) {
|
|
147
|
+
return event.text.startsWith(s.title) && event.y < s.maxY;
|
|
148
|
+
}
|
|
149
|
+
return event.text.startsWith(s.title);
|
|
150
|
+
});
|
|
165
151
|
|
|
166
152
|
// if there is a fallback provided, we will run it to try to detect another possible known step
|
|
167
153
|
if (!possibleKnownStep && description.fallback) {
|
|
@@ -211,7 +197,7 @@ const sep = " ";
|
|
|
211
197
|
export function formatDeviceAmount(
|
|
212
198
|
currency: CryptoCurrency | TokenCurrency,
|
|
213
199
|
value: BigNumber,
|
|
214
|
-
options: Partial<DeviceAmountFormatOptions> = defaultFormatOptions
|
|
200
|
+
options: Partial<DeviceAmountFormatOptions> = defaultFormatOptions,
|
|
215
201
|
): string {
|
|
216
202
|
const [unit] = currency.units;
|
|
217
203
|
let code = unit.code;
|
|
@@ -220,9 +206,7 @@ export function formatDeviceAmount(
|
|
|
220
206
|
if (deviceTicker) code = deviceTicker;
|
|
221
207
|
}
|
|
222
208
|
const fValue = value.div(new BigNumber(10).pow(unit.magnitude));
|
|
223
|
-
let v = options.showAllDigits
|
|
224
|
-
? fValue.toFixed(unit.magnitude)
|
|
225
|
-
: fValue.toString(10);
|
|
209
|
+
let v = options.showAllDigits ? fValue.toFixed(unit.magnitude) : fValue.toString(10);
|
|
226
210
|
if (options.forceFloating) {
|
|
227
211
|
if (!v.includes(".")) {
|
|
228
212
|
// if the value is pure integer, in the app it will automatically add an .0
|
|
@@ -238,20 +222,17 @@ export function formatDeviceAmount(
|
|
|
238
222
|
// Usage: put these in your spec, on the mutation transaction functions that intend to do more "delegations"
|
|
239
223
|
export function expectSiblingsHaveSpendablePartGreaterThan(
|
|
240
224
|
siblings: Account[],
|
|
241
|
-
threshold: number
|
|
225
|
+
threshold: number,
|
|
242
226
|
): void {
|
|
243
227
|
const spendableTotal = siblings.reduce(
|
|
244
228
|
(acc, a) => acc.plus(a.spendableBalance),
|
|
245
|
-
new BigNumber(0)
|
|
246
|
-
);
|
|
247
|
-
const total = siblings.reduce(
|
|
248
|
-
(acc, a) => acc.plus(a.balance),
|
|
249
|
-
new BigNumber(0)
|
|
229
|
+
new BigNumber(0),
|
|
250
230
|
);
|
|
231
|
+
const total = siblings.reduce((acc, a) => acc.plus(a.balance), new BigNumber(0));
|
|
251
232
|
invariant(
|
|
252
233
|
spendableTotal.div(total).gt(threshold),
|
|
253
234
|
"the spendable part of accounts is sufficient (threshold: %s)",
|
|
254
|
-
threshold
|
|
235
|
+
threshold,
|
|
255
236
|
);
|
|
256
237
|
}
|
|
257
238
|
|
|
@@ -264,8 +245,8 @@ export const genericTestDestination = <T>({
|
|
|
264
245
|
const amount = sendingOperation.value.minus(sendingOperation.fee);
|
|
265
246
|
botTest("account balance increased with transaction amount", () =>
|
|
266
247
|
expect(destination.balance.toString()).toBe(
|
|
267
|
-
destinationBeforeTransaction.balance.plus(amount).toString()
|
|
268
|
-
)
|
|
248
|
+
destinationBeforeTransaction.balance.plus(amount).toString(),
|
|
249
|
+
),
|
|
269
250
|
);
|
|
270
251
|
botTest("operation amount is consistent with sendingOperation", () =>
|
|
271
252
|
expect({
|
|
@@ -274,6 +255,6 @@ export const genericTestDestination = <T>({
|
|
|
274
255
|
}).toMatchObject({
|
|
275
256
|
type: "IN",
|
|
276
257
|
amount: amount.toString(),
|
|
277
|
-
})
|
|
258
|
+
}),
|
|
278
259
|
);
|
|
279
260
|
};
|
package/src/bot/types.ts
CHANGED
|
@@ -56,7 +56,7 @@ export type DeviceActionArg<T extends TransactionCommon, S> = {
|
|
|
56
56
|
disableStrictStepValueValidation?: boolean;
|
|
57
57
|
};
|
|
58
58
|
export type DeviceAction<T extends TransactionCommon, S> = (
|
|
59
|
-
arg0: DeviceActionArg<T, S
|
|
59
|
+
arg0: DeviceActionArg<T, S>,
|
|
60
60
|
) => S | null | undefined;
|
|
61
61
|
export type TransactionArg<T extends TransactionCommon> = {
|
|
62
62
|
appCandidate: AppCandidate;
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
import Transport from "@ledgerhq/hw-transport";
|
|
2
|
-
import {
|
|
3
|
-
DeviceAppVerifyNotSupported,
|
|
4
|
-
StatusCodes,
|
|
5
|
-
UserRefusedAddress,
|
|
6
|
-
} from "@ledgerhq/errors";
|
|
2
|
+
import { DeviceAppVerifyNotSupported, StatusCodes, UserRefusedAddress } from "@ledgerhq/errors";
|
|
7
3
|
import { log } from "@ledgerhq/logs";
|
|
8
4
|
import type { Result, GetAddressOptions } from "../derivation";
|
|
9
5
|
|
|
10
|
-
export type Resolver = (
|
|
11
|
-
transport: Transport,
|
|
12
|
-
addressOpt: GetAddressOptions
|
|
13
|
-
) => Promise<Result>;
|
|
6
|
+
export type Resolver = (transport: Transport, addressOpt: GetAddressOptions) => Promise<Result>;
|
|
14
7
|
|
|
15
8
|
const getAddressWrapper =
|
|
16
|
-
(getAddressFn: Resolver) =>
|
|
17
|
-
(transport: Transport, opts: GetAddressOptions) => {
|
|
9
|
+
(getAddressFn: Resolver) => (transport: Transport, opts: GetAddressOptions) => {
|
|
18
10
|
const { currency, path, verify } = opts;
|
|
19
11
|
return getAddressFn(transport, opts)
|
|
20
|
-
.then(
|
|
12
|
+
.then(result => {
|
|
21
13
|
log("hw", `getAddress ${currency.id} on ${path}`, result);
|
|
22
14
|
return result;
|
|
23
15
|
})
|
|
24
|
-
.catch(
|
|
16
|
+
.catch(e => {
|
|
25
17
|
log("hw", `getAddress ${currency.id} on ${path} FAILED ${String(e)}`);
|
|
26
18
|
|
|
27
19
|
if (e && e.name === "TransportStatusError") {
|