@ledgerhq/live-common 21.21.4 → 21.22.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 (62) hide show
  1. package/lib/apps/polyfill.d.ts.map +1 -1
  2. package/lib/apps/polyfill.js +1 -0
  3. package/lib/apps/polyfill.js.map +1 -1
  4. package/lib/bot/engine.js +1 -1
  5. package/lib/bot/engine.js.map +1 -1
  6. package/lib/bot/specs.d.ts +1 -0
  7. package/lib/bot/specs.d.ts.map +1 -1
  8. package/lib/bot/specs.js +3 -1
  9. package/lib/bot/specs.js.map +1 -1
  10. package/lib/env.js +1 -1
  11. package/lib/env.js.map +1 -1
  12. package/lib/families/tezos/bridge/js.d.ts.map +1 -1
  13. package/lib/families/tezos/bridge/js.js +84 -49
  14. package/lib/families/tezos/bridge/js.js.map +1 -1
  15. package/lib/families/tezos/bridge/libcore.d.ts.map +1 -1
  16. package/lib/families/tezos/bridge/libcore.js +2 -1
  17. package/lib/families/tezos/bridge/libcore.js.map +1 -1
  18. package/lib/families/tezos/bridge/mock.d.ts.map +1 -1
  19. package/lib/families/tezos/bridge/mock.js +2 -1
  20. package/lib/families/tezos/bridge/mock.js.map +1 -1
  21. package/lib/families/tezos/signOperation.d.ts.map +1 -1
  22. package/lib/families/tezos/signOperation.js +28 -19
  23. package/lib/families/tezos/signOperation.js.map +1 -1
  24. package/lib/families/tezos/specs.d.ts.map +1 -1
  25. package/lib/families/tezos/specs.js +8 -4
  26. package/lib/families/tezos/specs.js.map +1 -1
  27. package/lib/families/tezos/speculos-deviceActions.js +6 -7
  28. package/lib/families/tezos/speculos-deviceActions.js.map +1 -1
  29. package/lib/families/tezos/synchronisation.d.ts.map +1 -1
  30. package/lib/families/tezos/synchronisation.js +7 -4
  31. package/lib/families/tezos/synchronisation.js.map +1 -1
  32. package/lib/families/tezos/synchronisation.test.d.ts.map +1 -1
  33. package/lib/families/tezos/synchronisation.test.js +0 -3
  34. package/lib/families/tezos/synchronisation.test.js.map +1 -1
  35. package/lib/families/tezos/test-dataset.d.ts +2 -2
  36. package/lib/families/tezos/test-dataset.d.ts.map +1 -1
  37. package/lib/families/tezos/test-dataset.js +45 -319
  38. package/lib/families/tezos/test-dataset.js.map +1 -1
  39. package/lib/families/tezos/transaction.d.ts +2 -2
  40. package/lib/families/tezos/transaction.d.ts.map +1 -1
  41. package/lib/families/tezos/transaction.js +4 -4
  42. package/lib/families/tezos/transaction.js.map +1 -1
  43. package/lib/families/tezos/types.d.ts +2 -0
  44. package/lib/families/tezos/types.d.ts.map +1 -1
  45. package/lib/families/tezos/types.js.map +1 -1
  46. package/lib/generated/transaction.d.ts +1 -1
  47. package/package.json +9 -9
  48. package/src/apps/polyfill.ts +1 -0
  49. package/src/bot/engine.ts +1 -1
  50. package/src/bot/specs.ts +7 -1
  51. package/src/env.ts +1 -1
  52. package/src/families/tezos/bridge/js.ts +72 -35
  53. package/src/families/tezos/bridge/libcore.ts +1 -0
  54. package/src/families/tezos/bridge/mock.ts +1 -0
  55. package/src/families/tezos/signOperation.ts +19 -8
  56. package/src/families/tezos/specs.ts +16 -5
  57. package/src/families/tezos/speculos-deviceActions.ts +5 -5
  58. package/src/families/tezos/synchronisation.test.ts +0 -3
  59. package/src/families/tezos/synchronisation.ts +6 -3
  60. package/src/families/tezos/test-dataset.ts +33 -363
  61. package/src/families/tezos/transaction.ts +7 -1
  62. package/src/families/tezos/types.ts +2 -0
@@ -52,6 +52,7 @@ export const whitelistDependencies = ["Decred", "Decred Testnet"];
52
52
  ["RocketPool", "Ethereum"],
53
53
  ["POAP", "Ethereum"],
54
54
  ["OlympusDAO", "Ethereum"],
55
+ ["Rarible", "Ethereum"],
55
56
  ].forEach(([name, dep]) => declareDep(name, dep));
56
57
  export const getDependencies = (appName: string): string[] =>
57
58
  directDep[appName] || [];
package/src/bot/engine.ts CHANGED
@@ -678,7 +678,7 @@ function transactionTest<T>({
678
678
  const dt = Date.now().valueOf() - operation.date.valueOf();
679
679
  invariant(dt > 0, "operation.date must not be in in future");
680
680
  expect(dt).toBeLessThan(timingThreshold);
681
- invariant(!operation.hasFailed, "operation must be hasFailed");
681
+ invariant(!operation.hasFailed, "operation has failed");
682
682
  const { blockAvgTime } = account.currency;
683
683
 
684
684
  if (blockAvgTime && account.blockHeight) {
package/src/bot/specs.ts CHANGED
@@ -6,6 +6,9 @@ import sample from "lodash/sample";
6
6
  import { isAccountEmpty } from "../account";
7
7
  import type { DeviceAction, DeviceActionArg } from "./types";
8
8
  import type { Account, Transaction } from "../types";
9
+
10
+ const stepValueTransformDefault = (s) => s.trim();
11
+
9
12
  // TODO should weight the choice to favorize accounts with small amounts
10
13
  export function pickSiblings(siblings: Account[], maxAccount = 5): Account {
11
14
  const withoutEmpties = siblings.filter((a) => a.used);
@@ -37,6 +40,7 @@ type State<T extends Transaction> = {
37
40
  };
38
41
  type Step<T extends Transaction> = {
39
42
  title: string;
43
+ stepValueTransform?: (string) => string;
40
44
  expectedValue?: (
41
45
  arg0: DeviceActionArg<T, State<T>>,
42
46
  acc: Array<{
@@ -79,10 +83,12 @@ export function deviceActionFlow<T extends Transaction>(
79
83
  // there were accumulated text and we are on new step, we need to release it and compare to expected
80
84
  if (currentStep && currentStep.expectedValue) {
81
85
  const { expectedValue, ignoreAssertionFailure } = currentStep;
86
+ const stepValueTransform =
87
+ currentStep.stepValueTransform || stepValueTransformDefault;
82
88
 
83
89
  if (!ignoreAssertionFailure) {
84
90
  expect({
85
- [stepTitle]: stepValue.trim(),
91
+ [stepTitle]: stepValueTransform(stepValue),
86
92
  }).toMatchObject({
87
93
  [stepTitle]: expectedValue(arg, acc).trim(),
88
94
  });
package/src/env.ts CHANGED
@@ -111,7 +111,7 @@ const envDefinitions = {
111
111
  desc: "Ledger explorer API for tezos",
112
112
  },
113
113
  API_TEZOS_TZKT_API: {
114
- def: "http://xtz-tzkt-explorer.api-01.live.ledger-stg.com",
114
+ def: "https://xtz-tzkt-explorer.api.live.ledger.com",
115
115
  parser: stringParser,
116
116
  desc: "tzkt.io explorer",
117
117
  },
@@ -6,7 +6,6 @@ import {
6
6
  NotEnoughBalance,
7
7
  NotEnoughBalanceToDelegate,
8
8
  FeeTooHigh,
9
- NotSupportedLegacyAddress,
10
9
  InvalidAddressBecauseDestinationIsAlsoSource,
11
10
  RecommendUndelegation,
12
11
  } from "@ledgerhq/errors";
@@ -45,6 +44,7 @@ const createTransaction: () => Transaction = () => ({
45
44
  networkInfo: null,
46
45
  useAllAmount: false,
47
46
  taquitoError: null,
47
+ estimatedFees: null,
48
48
  });
49
49
 
50
50
  const updateTransaction = (t, patch) => ({ ...t, ...patch });
@@ -65,7 +65,7 @@ const getTransactionStatus = async (
65
65
  recipient?: Error;
66
66
  } = {};
67
67
 
68
- let estimatedFees = new BigNumber(t.fees || 0);
68
+ const estimatedFees = t.estimatedFees || new BigNumber(0);
69
69
 
70
70
  const { tezosResources } = account;
71
71
  if (!tezosResources) throw new Error("tezosResources is missing");
@@ -90,17 +90,6 @@ const getTransactionStatus = async (
90
90
  }
91
91
  }
92
92
 
93
- if (t.recipient.startsWith("KT") && !errors.recipient) {
94
- errors.recipient = new NotSupportedLegacyAddress();
95
- }
96
-
97
- // no fee / not enough balance already handled by taquitoError
98
- if (!tezosResources.revealed) {
99
- // FIXME
100
- // https://github.com/ecadlabs/taquito/commit/48a11cfaffe4c6bdfa6f04ebbd0b756f4b135865#diff-3b138622526cbaa55605b79011aa411652367136a3e92e43faecc654da3854e7
101
- estimatedFees = estimatedFees.plus(374 || DEFAULT_FEE.REVEAL);
102
- }
103
-
104
93
  if (t.mode === "send") {
105
94
  if (!errors.amount && t.amount.eq(0)) {
106
95
  errors.amount = new AmountRequired();
@@ -128,15 +117,22 @@ const getTransactionStatus = async (
128
117
  log("taquitoerror", String(t.taquitoError));
129
118
 
130
119
  // remap taquito errors
131
- if (t.taquitoError === "proto.010-PtGRANAD.contract.balance_too_low") {
120
+ if (t.taquitoError.endsWith("balance_too_low")) {
132
121
  if (t.mode === "send") {
133
122
  errors.amount = new NotEnoughBalance();
134
123
  } else {
135
124
  errors.amount = new NotEnoughBalanceToDelegate();
136
125
  }
126
+ } else {
127
+ // unidentified error case
128
+ errors.amount = new Error(t.taquitoError);
137
129
  }
138
130
  }
139
131
 
132
+ if (!errors.amount && account.balance.lte(0)) {
133
+ errors.amount = new NotEnoughBalance();
134
+ }
135
+
140
136
  const result = {
141
137
  errors,
142
138
  warnings,
@@ -154,8 +150,25 @@ const prepareTransaction = async (
154
150
  const { tezosResources } = account;
155
151
  if (!tezosResources) throw new Error("tezosResources is missing");
156
152
 
157
- const tezos = new TezosToolkit(getEnv("API_TEZOS_NODE"));
153
+ if (account.balance.lte(0)) {
154
+ return Promise.resolve(transaction);
155
+ }
158
156
 
157
+ // basic check to confirm the transaction is "complete"
158
+ if (transaction.mode !== "undelegate") {
159
+ if (!transaction.recipient) {
160
+ return Promise.resolve(transaction);
161
+ }
162
+ const { recipientError } = await validateRecipient(
163
+ account.currency,
164
+ transaction.recipient
165
+ );
166
+ if (recipientError) {
167
+ return Promise.resolve(transaction);
168
+ }
169
+ }
170
+
171
+ const tezos = new TezosToolkit(getEnv("API_TEZOS_NODE"));
159
172
  tezos.setProvider({
160
173
  signer: {
161
174
  publicKeyHash: async () => account.freshAddress,
@@ -165,20 +178,21 @@ const prepareTransaction = async (
165
178
  },
166
179
  });
167
180
 
168
- try {
169
- if (transaction.useAllAmount) {
170
- // taquito does not accept 0, so we do 1
171
- // only failing case is when the account precisely has fees + 1. which is
172
- // unlikely
173
- transaction.amount = new BigNumber(1);
174
- }
181
+ if (transaction.useAllAmount) {
182
+ // taquito does not accept 0, so we do 1
183
+ // only failing case is when the account precisely has fees + 1. which is
184
+ // unlikely
185
+ transaction.amount = new BigNumber(1);
186
+ }
175
187
 
188
+ try {
176
189
  let out;
177
190
  switch (transaction.mode) {
178
191
  case "send":
179
192
  out = await tezos.estimate.transfer({
193
+ mutez: true,
180
194
  to: transaction.recipient,
181
- amount: transaction.amount.div(10 ** 6).toNumber(),
195
+ amount: transaction.amount.toNumber(),
182
196
  });
183
197
  break;
184
198
  case "delegate":
@@ -193,14 +207,15 @@ const prepareTransaction = async (
193
207
  });
194
208
  break;
195
209
  default:
196
- throw "unsuported";
210
+ throw new Error("unsupported mode=" + transaction.mode);
197
211
  }
198
212
 
199
- transaction.fees = new BigNumber(out.suggestedFeeMutez);
200
- transaction.gasLimit = new BigNumber(out.gasLimit);
201
- transaction.storageLimit = new BigNumber(out.storageLimit);
202
-
203
213
  if (transaction.useAllAmount) {
214
+ const totalFees = out.suggestedFeeMutez + out.burnFeeMutez;
215
+ const maxAmount = account.balance
216
+ .minus(totalFees + (tezosResources.revealed ? 0 : DEFAULT_FEE.REVEAL))
217
+ .toNumber();
218
+ // from https://github.com/ecadlabs/taquito/blob/a70c64c4b105381bb9f1d04c9c70e8ef26e9241c/integration-tests/contract-empty-implicit-account-into-new-implicit-account.spec.ts#L33
204
219
  // Temporary fix, see https://gitlab.com/tezos/tezos/-/issues/1754
205
220
  // we need to increase the gasLimit and fee returned by the estimation
206
221
  const gasBuffer = 500;
@@ -208,15 +223,37 @@ const prepareTransaction = async (
208
223
  const increasedFee = (gasBuffer: number, opSize: number) => {
209
224
  return gasBuffer * MINIMAL_FEE_PER_GAS_MUTEZ + opSize;
210
225
  };
211
- transaction.fees = transaction.fees.plus(
212
- increasedFee(gasBuffer, Number(out.opSize))
226
+ const incr = increasedFee(gasBuffer, Number(out.opSize));
227
+ transaction.fees = new BigNumber(out.suggestedFeeMutez + incr);
228
+ transaction.gasLimit = new BigNumber(out.gasLimit + gasBuffer);
229
+ transaction.amount = new BigNumber(maxAmount - incr);
230
+ } else {
231
+ transaction.fees = new BigNumber(out.suggestedFeeMutez);
232
+ transaction.gasLimit = new BigNumber(out.gasLimit);
233
+ transaction.storageLimit = new BigNumber(out.storageLimit);
234
+ }
235
+
236
+ transaction.storageLimit = new BigNumber(out.storageLimit);
237
+ transaction.estimatedFees = transaction.fees;
238
+ if (!tezosResources.revealed) {
239
+ transaction.estimatedFees = transaction.estimatedFees.plus(
240
+ DEFAULT_FEE.REVEAL
241
+ );
242
+ }
243
+ } catch (e) {
244
+ if (typeof e !== "object" || !e) throw e;
245
+ if ("id" in e) {
246
+ transaction.taquitoError = (e as { id: string }).id;
247
+ } else if ("status" in e) {
248
+ // in case of http 400, log & ignore (more case to handle)
249
+ log(
250
+ "taquito-network-error",
251
+ String((e as { message: string }).message || ""),
252
+ { transaction }
213
253
  );
214
- transaction.gasLimit = transaction.gasLimit.plus(gasBuffer);
215
- const s = await getTransactionStatus(account, transaction);
216
- transaction.amount = account.balance.minus(s.estimatedFees);
254
+ } else {
255
+ throw e;
217
256
  }
218
- } catch (e: any) {
219
- transaction.taquitoError = e.id;
220
257
  }
221
258
 
222
259
  return transaction;
@@ -90,6 +90,7 @@ const createTransaction = (): Transaction => ({
90
90
  networkInfo: null,
91
91
  useAllAmount: false,
92
92
  taquitoError: null,
93
+ estimatedFees: null,
93
94
  });
94
95
 
95
96
  const updateTransaction = (t, patch) => ({ ...t, ...patch });
@@ -64,6 +64,7 @@ const createTransaction = (): Transaction => ({
64
64
  networkInfo: null,
65
65
  useAllAmount: false,
66
66
  taquitoError: null,
67
+ estimatedFees: null,
67
68
  });
68
69
 
69
70
  const updateTransaction = (t, patch) => ({ ...t, ...patch });
@@ -38,40 +38,51 @@ export const signOperation = ({
38
38
  );
39
39
  tezos.setProvider({ signer: ledgerSigner });
40
40
 
41
+ // disable the broadcast because we want to do it in a second phase (broadcast hook)
41
42
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
42
43
  // @ts-ignore
43
- tezos.contract.context.injector.inject = async () => ""; // disable broadcast
44
+ tezos.contract.context.injector.inject = async () => "";
44
45
 
45
46
  o.next({ type: "device-signature-requested" });
46
47
 
48
+ let type = "OUT";
49
+
47
50
  let res, signature, opbytes;
51
+ const params = {
52
+ fee: transaction.fees?.toNumber() || 0,
53
+ storageLimit: transaction.storageLimit?.toNumber() || 0,
54
+ gasLimit: transaction.gasLimit?.toNumber() || 0,
55
+ };
48
56
  switch (transaction.mode) {
49
57
  case "send":
50
58
  res = await tezos.contract.transfer({
59
+ mutez: true,
51
60
  to: transaction.recipient,
52
- amount: transaction.amount.div(10 ** 6).toNumber(),
53
- fee: transaction.fees?.toNumber() || 0,
54
- storageLimit: transaction.storageLimit?.toNumber() || 0,
55
- gasLimit: transaction.gasLimit?.toNumber() || 0,
61
+ amount: transaction.amount.toNumber(),
62
+ ...params,
56
63
  });
57
64
  signature = res.raw.opOb.signature;
58
65
  opbytes = res.raw.opbytes;
59
66
  break;
60
67
  case "delegate":
61
68
  res = await tezos.contract.setDelegate({
69
+ ...params,
62
70
  source: freshAddress,
63
71
  delegate: transaction.recipient,
64
72
  });
65
73
  opbytes = res.raw.opbytes;
74
+ type = "DELEGATE";
66
75
  break;
67
76
  case "undelegate":
68
77
  res = await tezos.contract.setDelegate({
78
+ ...params,
69
79
  source: freshAddress,
70
80
  });
71
81
  opbytes = res.raw.opbytes;
82
+ type = "UNDELEGATE";
72
83
  break;
73
84
  default:
74
- throw "not supported";
85
+ throw new Error("not supported");
75
86
  }
76
87
 
77
88
  if (cancelled) {
@@ -88,9 +99,9 @@ export const signOperation = ({
88
99
 
89
100
  // currently, all mode are always at least one OUT tx on ETH parent
90
101
  const operation = {
91
- id: `${accountId}-${txHash}-OUT`,
102
+ id: `${accountId}-${txHash}-${type}`,
92
103
  hash: txHash,
93
- type: "OUT",
104
+ type,
94
105
  value: transaction.amount,
95
106
  fee: fees,
96
107
  extra: {
@@ -6,7 +6,7 @@ import { getCryptoCurrencyById, parseCurrencyUnit } from "../../currencies";
6
6
  import { pickSiblings } from "../../bot/specs";
7
7
  import type { AppSpec } from "../../bot/types";
8
8
  import { DeviceModelId } from "@ledgerhq/devices";
9
- import { getAccountDelegationSync } from "./bakers";
9
+ import { getAccountDelegationSync, isAccountDelegating } from "./bakers";
10
10
  import whitelist from "./bakers.whitelist-default";
11
11
 
12
12
  const maxAccount = 12;
@@ -31,10 +31,11 @@ const tezos: AppSpec<Transaction> = {
31
31
  model: DeviceModelId.nanoS,
32
32
  appName: "TezosWallet",
33
33
  },
34
+ testTimeout: 2 * 60 * 1000,
34
35
  transactionCheck: ({ maxSpendable }) => {
35
36
  invariant(
36
37
  maxSpendable.gt(
37
- parseCurrencyUnit(getCryptoCurrencyById("tezos").units[0], "0.1")
38
+ parseCurrencyUnit(getCryptoCurrencyById("tezos").units[0], "0.02")
38
39
  ),
39
40
  "balance is too low"
40
41
  );
@@ -69,9 +70,13 @@ const tezos: AppSpec<Transaction> = {
69
70
  },
70
71
  },
71
72
  {
72
- name: "send max",
73
+ name: "send max (non delegating)",
73
74
  maxRun: 3,
74
75
  transaction: ({ account, siblings, bridge }) => {
76
+ invariant(
77
+ !isAccountDelegating(account),
78
+ "account must not be delegating"
79
+ );
75
80
  const sibling = pickSiblings(siblings, maxAccount);
76
81
  const recipient = sibling.freshAddress;
77
82
  return {
@@ -85,7 +90,10 @@ const tezos: AppSpec<Transaction> = {
85
90
  maxRun: 1,
86
91
  transaction: ({ account, bridge }) => {
87
92
  expectUnrevealed(account);
88
- const recipient = sample(whitelist);
93
+ const d = getAccountDelegationSync(account);
94
+ const recipient = sample(
95
+ d ? whitelist.filter((w) => w !== d.address) : whitelist
96
+ );
89
97
  return {
90
98
  transaction: bridge.createTransaction(account),
91
99
  updates: [{ recipient, mode: "delegate" }],
@@ -97,7 +105,10 @@ const tezos: AppSpec<Transaction> = {
97
105
  maxRun: 1,
98
106
  transaction: ({ account, bridge }) => {
99
107
  expectRevealed(account);
100
- const recipient = sample(whitelist);
108
+ const d = getAccountDelegationSync(account);
109
+ const recipient = sample(
110
+ d ? whitelist.filter((w) => w !== d.address) : whitelist
111
+ );
101
112
  return {
102
113
  transaction: bridge.createTransaction(account),
103
114
  updates: [{ recipient, mode: "delegate" }],
@@ -15,13 +15,13 @@ const acceptTransaction: DeviceAction<Transaction, any> = deviceActionFlow({
15
15
  button: "Rr",
16
16
  },
17
17
  {
18
+ title: "Custom Delegate",
19
+ button: "Rr",
20
+ },
21
+ {
22
+ // device sometimes know the text of a given delegator, we're not validating the actual text
18
23
  title: "Delegate",
19
24
  button: "Rr",
20
- expectedValue: ({ transaction }) => {
21
- return transaction.mode === "undelegate"
22
- ? "None"
23
- : transaction.recipient;
24
- },
25
25
  },
26
26
  {
27
27
  title: "Delegate Name",
@@ -9,8 +9,6 @@ import { makeSync } from "../../bridge/jsHelpers";
9
9
  import { getAccountShape } from "./synchronisation";
10
10
 
11
11
  const accounts = [
12
- // "tz1cf8diKsP1WoeucTQm1BcVfdyrmaBnL4nC", // FIXME
13
- // "tz1ZshTmtorFVkcZ7CpceCAxCn7HBJqTfmpk", // FIXME
14
12
  "tz1ZL36Xw6LJ9RK6tb7QW85o7SVYfFKSMw8c",
15
13
  "tz1VacAyLcnHU3pEY3VNNK45F1CvVyTYXA3z",
16
14
  "tz1hpWkvAVVgftPy4CW8rD3u6tdVd8g1RU58",
@@ -24,7 +22,6 @@ const accounts = [
24
22
  "tz1PTxfn1fge2tJwGGpW9zNuYf6BseM3GJXt",
25
23
  "tz1SBHto1BasGvxojr48r5PFiGb1Wq4cynGY",
26
24
  "tz1fAfYNSkgci3ko2xikDtYtcSf1X1YdAKoK",
27
- "tz1hobi4RsAeswm9JA7mHB96vqc1eAWRzxDc",
28
25
  ];
29
26
 
30
27
  jest.setTimeout(30 * 1000);
@@ -75,6 +75,11 @@ export const getAccountShape: GetAccountShape = async (infoInput) => {
75
75
  id: accountId,
76
76
  blockHeight,
77
77
  lastSyncDate: new Date(),
78
+ tezosResources: {
79
+ revealed: false,
80
+ counter: 0,
81
+ publicKey: "",
82
+ },
78
83
  };
79
84
  }
80
85
  invariant(
@@ -232,9 +237,7 @@ const txToOp =
232
237
  blockHash,
233
238
  accountId,
234
239
  date: new Date(timestamp),
235
- extra: {
236
- id,
237
- },
240
+ extra: { id },
238
241
  hasFailed,
239
242
  };
240
243
  };