@ledgerhq/live-common 21.13.2 → 21.16.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 (221) hide show
  1. package/lib/__tests__/derivation.js +1 -1
  2. package/lib/__tests__/derivation.js.map +1 -1
  3. package/lib/__tests__/families/bitcoin/wallet-btc/wallet.integration.test.js +2 -2
  4. package/lib/account/groupOperations.js +1 -1
  5. package/lib/account/groupOperations.js.map +1 -1
  6. package/lib/account/pending.d.ts +1 -0
  7. package/lib/account/pending.d.ts.map +1 -1
  8. package/lib/account/serialization.d.ts +6 -4
  9. package/lib/account/serialization.d.ts.map +1 -1
  10. package/lib/account/serialization.js +47 -9
  11. package/lib/account/serialization.js.map +1 -1
  12. package/lib/api/Ethereum.d.ts +22 -1
  13. package/lib/api/Ethereum.d.ts.map +1 -1
  14. package/lib/api/Ethereum.js +21 -1
  15. package/lib/api/Ethereum.js.map +1 -1
  16. package/lib/apps/polyfill.d.ts.map +1 -1
  17. package/lib/apps/polyfill.js +1 -0
  18. package/lib/apps/polyfill.js.map +1 -1
  19. package/lib/bridge/jsHelpers.d.ts +2 -1
  20. package/lib/bridge/jsHelpers.d.ts.map +1 -1
  21. package/lib/bridge/jsHelpers.js +27 -1
  22. package/lib/bridge/jsHelpers.js.map +1 -1
  23. package/lib/csvExport.d.ts.map +1 -1
  24. package/lib/csvExport.js +3 -1
  25. package/lib/csvExport.js.map +1 -1
  26. package/lib/currencies/sortByMarketcap.d.ts.map +1 -1
  27. package/lib/currencies/sortByMarketcap.js +8 -1
  28. package/lib/currencies/sortByMarketcap.js.map +1 -1
  29. package/lib/derivation.js +1 -1
  30. package/lib/derivation.js.map +1 -1
  31. package/lib/env.d.ts +15 -5
  32. package/lib/env.d.ts.map +1 -1
  33. package/lib/env.js +12 -1
  34. package/lib/env.js.map +1 -1
  35. package/lib/env.react.d.ts +1 -1
  36. package/lib/env.react.d.ts.map +1 -1
  37. package/lib/families/bitcoin/cache.d.ts +1 -0
  38. package/lib/families/bitcoin/cache.d.ts.map +1 -1
  39. package/lib/families/bitcoin/cache.js +7 -1
  40. package/lib/families/bitcoin/cache.js.map +1 -1
  41. package/lib/families/bitcoin/errors.d.ts +4 -0
  42. package/lib/families/bitcoin/errors.d.ts.map +1 -0
  43. package/lib/families/bitcoin/errors.js +6 -0
  44. package/lib/families/bitcoin/errors.js.map +1 -0
  45. package/lib/families/bitcoin/js-getTransactionStatus.d.ts.map +1 -1
  46. package/lib/families/bitcoin/js-getTransactionStatus.js +24 -11
  47. package/lib/families/bitcoin/js-getTransactionStatus.js.map +1 -1
  48. package/lib/families/bitcoin/js-synchronisation.d.ts.map +1 -1
  49. package/lib/families/bitcoin/js-synchronisation.js +8 -19
  50. package/lib/families/bitcoin/js-synchronisation.js.map +1 -1
  51. package/lib/families/bitcoin/logic.d.ts +1 -1
  52. package/lib/families/bitcoin/logic.d.ts.map +1 -1
  53. package/lib/families/bitcoin/logic.js +6 -0
  54. package/lib/families/bitcoin/logic.js.map +1 -1
  55. package/lib/families/bitcoin/specs.d.ts.map +1 -1
  56. package/lib/families/bitcoin/specs.js +2 -1
  57. package/lib/families/bitcoin/specs.js.map +1 -1
  58. package/lib/families/bitcoin/test-dataset.js +2 -2
  59. package/lib/families/bitcoin/test-dataset.js.map +1 -1
  60. package/lib/families/bitcoin/wallet-btc/crypto/base.d.ts +4 -0
  61. package/lib/families/bitcoin/wallet-btc/crypto/base.d.ts.map +1 -1
  62. package/lib/families/bitcoin/wallet-btc/crypto/base.js +36 -5
  63. package/lib/families/bitcoin/wallet-btc/crypto/base.js.map +1 -1
  64. package/lib/families/bitcoin/wallet-btc/crypto/bitcoin.d.ts +1 -0
  65. package/lib/families/bitcoin/wallet-btc/crypto/bitcoin.d.ts.map +1 -1
  66. package/lib/families/bitcoin/wallet-btc/crypto/bitcoin.js +19 -1
  67. package/lib/families/bitcoin/wallet-btc/crypto/bitcoin.js.map +1 -1
  68. package/lib/families/bitcoin/wallet-btc/crypto/bitcoincash.d.ts +1 -0
  69. package/lib/families/bitcoin/wallet-btc/crypto/bitcoincash.d.ts.map +1 -1
  70. package/lib/families/bitcoin/wallet-btc/crypto/bitcoincash.js +4 -0
  71. package/lib/families/bitcoin/wallet-btc/crypto/bitcoincash.js.map +1 -1
  72. package/lib/families/bitcoin/wallet-btc/crypto/types.d.ts +1 -0
  73. package/lib/families/bitcoin/wallet-btc/crypto/types.d.ts.map +1 -1
  74. package/lib/families/bitcoin/wallet-btc/crypto/zec.d.ts +1 -0
  75. package/lib/families/bitcoin/wallet-btc/crypto/zec.d.ts.map +1 -1
  76. package/lib/families/bitcoin/wallet-btc/crypto/zec.js +4 -0
  77. package/lib/families/bitcoin/wallet-btc/crypto/zec.js.map +1 -1
  78. package/lib/families/bitcoin/wallet-btc/crypto/zen.d.ts +1 -0
  79. package/lib/families/bitcoin/wallet-btc/crypto/zen.d.ts.map +1 -1
  80. package/lib/families/bitcoin/wallet-btc/crypto/zen.js +4 -0
  81. package/lib/families/bitcoin/wallet-btc/crypto/zen.js.map +1 -1
  82. package/lib/families/bitcoin/wallet-btc/explorer/index.d.ts.map +1 -1
  83. package/lib/families/bitcoin/wallet-btc/explorer/index.js +0 -3
  84. package/lib/families/bitcoin/wallet-btc/explorer/index.js.map +1 -1
  85. package/lib/families/bitcoin/wallet-btc/index.d.ts +2 -2
  86. package/lib/families/bitcoin/wallet-btc/index.d.ts.map +1 -1
  87. package/lib/families/bitcoin/wallet-btc/index.js +4 -5
  88. package/lib/families/bitcoin/wallet-btc/index.js.map +1 -1
  89. package/lib/families/bitcoin/wallet-btc/pickingstrategies/CoinSelect.js +3 -3
  90. package/lib/families/bitcoin/wallet-btc/pickingstrategies/DeepFirst.js +5 -5
  91. package/lib/families/bitcoin/wallet-btc/pickingstrategies/Merge.js +5 -5
  92. package/lib/families/bitcoin/wallet-btc/utils.d.ts +3 -1
  93. package/lib/families/bitcoin/wallet-btc/utils.d.ts.map +1 -1
  94. package/lib/families/bitcoin/wallet-btc/utils.js +30 -10
  95. package/lib/families/bitcoin/wallet-btc/utils.js.map +1 -1
  96. package/lib/families/bitcoin/wallet-btc/wallet.d.ts.map +1 -1
  97. package/lib/families/bitcoin/wallet-btc/wallet.js +5 -3
  98. package/lib/families/bitcoin/wallet-btc/wallet.js.map +1 -1
  99. package/lib/families/bitcoin/wallet-btc/xpub.d.ts +2 -6
  100. package/lib/families/bitcoin/wallet-btc/xpub.d.ts.map +1 -1
  101. package/lib/families/bitcoin/wallet-btc/xpub.js +62 -52
  102. package/lib/families/bitcoin/wallet-btc/xpub.js.map +1 -1
  103. package/lib/families/elrond/js-getTransactionStatus.d.ts.map +1 -1
  104. package/lib/families/elrond/js-getTransactionStatus.js +3 -0
  105. package/lib/families/elrond/js-getTransactionStatus.js.map +1 -1
  106. package/lib/families/elrond/js-signOperation.d.ts.map +1 -1
  107. package/lib/families/elrond/js-signOperation.js +3 -1
  108. package/lib/families/elrond/js-signOperation.js.map +1 -1
  109. package/lib/families/ethereum/signOperation.d.ts.map +1 -1
  110. package/lib/families/ethereum/signOperation.js +7 -0
  111. package/lib/families/ethereum/signOperation.js.map +1 -1
  112. package/lib/families/ethereum/specs.d.ts +1 -0
  113. package/lib/families/ethereum/specs.d.ts.map +1 -1
  114. package/lib/families/ethereum/specs.js +41 -0
  115. package/lib/families/ethereum/specs.js.map +1 -1
  116. package/lib/families/ethereum/synchronisation.d.ts.map +1 -1
  117. package/lib/families/ethereum/synchronisation.js +175 -15
  118. package/lib/families/ethereum/synchronisation.js.map +1 -1
  119. package/lib/families/ethereum/versions.d.ts +2 -0
  120. package/lib/families/ethereum/versions.d.ts.map +1 -0
  121. package/lib/families/ethereum/versions.js +5 -0
  122. package/lib/families/ethereum/versions.js.map +1 -0
  123. package/lib/generated/specs.d.ts +1 -0
  124. package/lib/generated/specs.d.ts.map +1 -1
  125. package/lib/nft/NftMetadataProvider/index.d.ts +10 -0
  126. package/lib/nft/NftMetadataProvider/index.d.ts.map +1 -0
  127. package/lib/nft/NftMetadataProvider/index.js +244 -0
  128. package/lib/nft/NftMetadataProvider/index.js.map +1 -0
  129. package/lib/nft/NftMetadataProvider/logic.d.ts +3 -0
  130. package/lib/nft/NftMetadataProvider/logic.d.ts.map +1 -0
  131. package/lib/nft/NftMetadataProvider/logic.js +22 -0
  132. package/lib/nft/NftMetadataProvider/logic.js.map +1 -0
  133. package/lib/nft/NftMetadataProvider/types.d.ts +44 -0
  134. package/lib/nft/NftMetadataProvider/types.d.ts.map +1 -0
  135. package/lib/nft/NftMetadataProvider/types.js +3 -0
  136. package/lib/nft/NftMetadataProvider/types.js.map +1 -0
  137. package/lib/nft/helpers.d.ts +10 -0
  138. package/lib/nft/helpers.d.ts.map +1 -0
  139. package/lib/nft/helpers.js +85 -0
  140. package/lib/nft/helpers.js.map +1 -0
  141. package/lib/nft/index.d.ts +4 -0
  142. package/lib/nft/index.d.ts.map +1 -0
  143. package/lib/nft/index.js +16 -0
  144. package/lib/nft/index.js.map +1 -0
  145. package/lib/nft/nftId.d.ts +3 -0
  146. package/lib/nft/nftId.d.ts.map +1 -0
  147. package/lib/nft/nftId.js +33 -0
  148. package/lib/nft/nftId.js.map +1 -0
  149. package/lib/operation.d.ts +1 -1
  150. package/lib/operation.d.ts.map +1 -1
  151. package/lib/operation.js +19 -5
  152. package/lib/operation.js.map +1 -1
  153. package/lib/reconciliation.d.ts.map +1 -1
  154. package/lib/reconciliation.js +6 -0
  155. package/lib/reconciliation.js.map +1 -1
  156. package/lib/types/account.d.ts +3 -0
  157. package/lib/types/account.d.ts.map +1 -1
  158. package/lib/types/index.d.ts +1 -0
  159. package/lib/types/index.d.ts.map +1 -1
  160. package/lib/types/index.js +1 -0
  161. package/lib/types/index.js.map +1 -1
  162. package/lib/types/nft.d.ts +29 -0
  163. package/lib/types/nft.d.ts.map +1 -0
  164. package/lib/types/nft.js +3 -0
  165. package/lib/types/nft.js.map +1 -0
  166. package/lib/types/operation.d.ts +12 -1
  167. package/lib/types/operation.d.ts.map +1 -1
  168. package/package.json +13 -13
  169. package/src/__tests__/__snapshots__/all.libcore.ts.snap +64428 -57815
  170. package/src/__tests__/derivation.ts +1 -1
  171. package/src/__tests__/families/bitcoin/wallet-btc/wallet.integration.test.ts +2 -2
  172. package/src/account/groupOperations.ts +2 -2
  173. package/src/account/serialization.ts +52 -0
  174. package/src/api/Ethereum.ts +40 -2
  175. package/src/apps/polyfill.ts +1 -0
  176. package/src/bridge/jsHelpers.ts +35 -0
  177. package/src/csvExport.ts +3 -2
  178. package/src/currencies/__snapshots__/sortByMarketcap.test.ts.snap +1361 -1
  179. package/src/currencies/sortByMarketcap.ts +9 -1
  180. package/src/derivation.ts +1 -1
  181. package/src/env.ts +12 -1
  182. package/src/families/bitcoin/cache.ts +12 -0
  183. package/src/families/bitcoin/errors.ts +5 -0
  184. package/src/families/bitcoin/js-getTransactionStatus.ts +15 -1
  185. package/src/families/bitcoin/js-synchronisation.ts +11 -25
  186. package/src/families/bitcoin/logic.ts +7 -2
  187. package/src/families/bitcoin/specs.ts +6 -1
  188. package/src/families/bitcoin/test-dataset.ts +2 -2
  189. package/src/families/bitcoin/wallet-btc/crypto/base.ts +40 -5
  190. package/src/families/bitcoin/wallet-btc/crypto/bitcoin.ts +21 -1
  191. package/src/families/bitcoin/wallet-btc/crypto/bitcoincash.ts +5 -0
  192. package/src/families/bitcoin/wallet-btc/crypto/types.ts +1 -0
  193. package/src/families/bitcoin/wallet-btc/crypto/zec.ts +5 -0
  194. package/src/families/bitcoin/wallet-btc/crypto/zen.ts +5 -0
  195. package/src/families/bitcoin/wallet-btc/explorer/index.ts +0 -5
  196. package/src/families/bitcoin/wallet-btc/index.ts +4 -4
  197. package/src/families/bitcoin/wallet-btc/pickingstrategies/CoinSelect.ts +3 -3
  198. package/src/families/bitcoin/wallet-btc/pickingstrategies/DeepFirst.ts +5 -5
  199. package/src/families/bitcoin/wallet-btc/pickingstrategies/Merge.ts +5 -5
  200. package/src/families/bitcoin/wallet-btc/utils.ts +34 -9
  201. package/src/families/bitcoin/wallet-btc/wallet.ts +4 -2
  202. package/src/families/bitcoin/wallet-btc/xpub.ts +37 -53
  203. package/src/families/elrond/js-getTransactionStatus.ts +4 -0
  204. package/src/families/elrond/js-signOperation.ts +3 -1
  205. package/src/families/ethereum/modules/__snapshots__/compound.resilience.test.ts.snap +2876 -0
  206. package/src/families/ethereum/signOperation.ts +8 -0
  207. package/src/families/ethereum/specs.ts +47 -0
  208. package/src/families/ethereum/synchronisation.ts +185 -6
  209. package/src/families/ethereum/versions.ts +1 -0
  210. package/src/nft/NftMetadataProvider/index.tsx +205 -0
  211. package/src/nft/NftMetadataProvider/logic.ts +20 -0
  212. package/src/nft/NftMetadataProvider/types.ts +60 -0
  213. package/src/nft/helpers.ts +78 -0
  214. package/src/nft/index.ts +3 -0
  215. package/src/nft/nftId.ts +17 -0
  216. package/src/operation.ts +22 -3
  217. package/src/reconciliation.ts +7 -0
  218. package/src/types/account.ts +4 -0
  219. package/src/types/index.ts +1 -0
  220. package/src/types/nft.ts +38 -0
  221. package/src/types/operation.ts +23 -1
@@ -81,7 +81,15 @@ export const getMarketcapTickers: () => Promise<string[]> = makeLRUCache(() =>
81
81
  export const useMarketcapTickers = (): string[] | null | undefined => {
82
82
  const [tickers, setMarketcapTickers] = useState(marketcapTickersCache);
83
83
  useEffect(() => {
84
- getMarketcapTickers().then(setMarketcapTickers);
84
+ let isMounted = true;
85
+
86
+ getMarketcapTickers().then((data) => {
87
+ if (isMounted) setMarketcapTickers(data);
88
+ });
89
+
90
+ return () => {
91
+ isMounted = false;
92
+ };
85
93
  }, []);
86
94
  return tickers;
87
95
  };
package/src/derivation.ts CHANGED
@@ -470,7 +470,7 @@ export const getDerivationModesForCurrency = (
470
470
 
471
471
  // taproot logic. FIXME should move per family
472
472
  if (currency.family === "bitcoin") {
473
- if (currency.id === "bitcoin_testnet") {
473
+ if (currency.id === "bitcoin" || currency.id === "bitcoin_testnet") {
474
474
  all.push("taproot");
475
475
  }
476
476
  }
package/src/env.ts CHANGED
@@ -67,7 +67,7 @@ const envDefinitions = {
67
67
  },
68
68
  API_RIPPLE_WS: {
69
69
  parser: stringParser,
70
- def: "wss://xrplcluster.com",
70
+ def: "wss://xrplcluster.com/ledgerlive",
71
71
  desc: "XRP Ledger full history open WebSocket endpoint",
72
72
  },
73
73
  API_FILECOIN_ENDPOINT: {
@@ -376,6 +376,17 @@ const envDefinitions = {
376
376
  parser: stringParser,
377
377
  desc: "mock the server response for the exchange KYC check, options are 'open', 'pending', 'closed' or 'approved'.",
378
378
  },
379
+ NFT: {
380
+ def: false,
381
+ parser: boolParser,
382
+ desc: "synchronizing nfts",
383
+ },
384
+ NFT_ETH_METADATA_SERVICE: {
385
+ // FIXME LL-8001
386
+ def: "https://nft.staging.aws.ledger.fr/v1/ethereum/1/contracts/tokens/infos",
387
+ parser: stringParser,
388
+ desc: "service uri used to get the metadata of an nft",
389
+ },
379
390
  OPERATION_ADDRESSES_LIMIT: {
380
391
  def: 100,
381
392
  parser: intParser,
@@ -4,6 +4,7 @@ import type { Account, CryptoCurrency } from "./../../types";
4
4
  import type { Transaction } from "./types";
5
5
  import getFeesForTransaction from "./js-getFeesForTransaction";
6
6
  import { isValidRecipient } from "./logic";
7
+ import { Currency, isTaprootAddress } from "./wallet-btc";
7
8
 
8
9
  const getCacheKeyForCalculateFees = ({
9
10
  a,
@@ -41,6 +42,7 @@ export const calculateFees = makeLRUCache(
41
42
  maxAge: 5 * 60 * 1000, // 5 minutes
42
43
  }
43
44
  );
45
+
44
46
  export const validateRecipient: (
45
47
  arg0: CryptoCurrency,
46
48
  arg1: string | null | undefined
@@ -74,3 +76,13 @@ export const validateRecipient: (
74
76
  },
75
77
  (currency, recipient) => `${currency.id}_${recipient || ""}`
76
78
  );
79
+
80
+ export const isTaprootRecipient: (
81
+ arg0: CryptoCurrency,
82
+ arg1: string
83
+ ) => Promise<boolean> = makeLRUCache(
84
+ async (currency, recipient) => {
85
+ return isTaprootAddress(recipient, <Currency>currency.id);
86
+ },
87
+ (currency, recipient) => `${currency.id}_${recipient || ""}`
88
+ );
@@ -0,0 +1,5 @@
1
+ import { createCustomErrorClass } from "@ledgerhq/errors";
2
+
3
+ export const TaprootNotActivated = createCustomErrorClass(
4
+ "TaprootNotActivated"
5
+ );
@@ -9,7 +9,8 @@ import {
9
9
  } from "@ledgerhq/errors";
10
10
  import type { Account, TransactionStatus } from "./../../types";
11
11
  import type { Transaction } from "./types";
12
- import { calculateFees, validateRecipient } from "./cache";
12
+ import { calculateFees, validateRecipient, isTaprootRecipient } from "./cache";
13
+ import { TaprootNotActivated } from "./errors";
13
14
 
14
15
  const getTransactionStatus = async (
15
16
  a: Account,
@@ -31,6 +32,19 @@ const getTransactionStatus = async (
31
32
  warnings.recipient = recipientWarning;
32
33
  }
33
34
 
35
+ // Safeguard before Taproot activation
36
+ if (
37
+ t.recipient &&
38
+ !errors.recipient &&
39
+ a.currency.id === "bitcoin" &&
40
+ a.blockHeight <= 709632
41
+ ) {
42
+ const isTaproot = await isTaprootRecipient(a.currency, t.recipient);
43
+ if (isTaproot) {
44
+ errors.recipient = new TaprootNotActivated();
45
+ }
46
+ }
47
+
34
48
  let txInputs;
35
49
  let txOutputs;
36
50
  let estimatedFees = new BigNumber(0);
@@ -128,34 +128,20 @@ const mapTxToOperations = (
128
128
  // All inputs of a same transaction have the same sequence
129
129
  const transactionSequenceNumber =
130
130
  (accountInputs.length > 0 && accountInputs[0].sequence) || undefined;
131
- const hasSpentNothing = value.eq(0);
131
+
132
+ // Change output is the last one
133
+ const changeOutputIndex = tx.outputs
134
+ .map((o) => o.output_index)
135
+ .reduce((p, c) => (p > c ? p : c));
132
136
 
133
137
  for (const output of tx.outputs) {
134
138
  if (output.address) {
135
139
  if (accountAddresses.includes(output.address)) {
136
140
  accountOutputs.push(output);
137
-
138
- if (changeAddresses.includes(output.address)) {
139
- if (
140
- (recipients.length === 0 &&
141
- output.output_hash ===
142
- tx.outputs[tx.outputs.length - 1].output_hash) ||
143
- hasSpentNothing
144
- ) {
145
- recipients.push(
146
- syncReplaceAddress
147
- ? syncReplaceAddress(output.address)
148
- : output.address
149
- );
150
- }
151
- } else {
152
- recipients.push(
153
- syncReplaceAddress
154
- ? syncReplaceAddress(output.address)
155
- : output.address
156
- );
157
- }
158
- } else {
141
+ } else if (
142
+ !changeAddresses.includes(output.address) ||
143
+ output.output_index !== changeOutputIndex
144
+ ) {
159
145
  recipients.push(
160
146
  syncReplaceAddress
161
147
  ? syncReplaceAddress(output.address)
@@ -342,8 +328,8 @@ const getAccountShape: GetAccountShape = async (info) => {
342
328
  spendableBalance: balance,
343
329
  operations,
344
330
  operationsCount: operations.length,
345
- freshAddress: walletAccount.xpub.freshAddress[0],
346
- freshAddressPath: `${accountPath}/0/${walletAccount.xpub.freshAddressIndex[0]}`,
331
+ freshAddress: walletAccount.xpub.freshAddress,
332
+ freshAddressPath: `${accountPath}/0/${walletAccount.xpub.freshAddressIndex}`,
347
333
  blockHeight,
348
334
  bitcoinResources: {
349
335
  utxos,
@@ -79,7 +79,7 @@ export function isChangeOutput(output: BitcoinOutput): boolean {
79
79
  type UTXOStatus =
80
80
  | {
81
81
  excluded: true;
82
- reason: "pickUnconfirmedRBF" | "userExclusion";
82
+ reason: "pickUnconfirmedRBF" | "pickPendingNonRBF" | "userExclusion";
83
83
  }
84
84
  | {
85
85
  excluded: false;
@@ -94,7 +94,12 @@ export const getUTXOStatus = (
94
94
  reason: "pickUnconfirmedRBF",
95
95
  };
96
96
  }
97
-
97
+ if (!utxo.rbf && !utxo.blockHeight) {
98
+ return {
99
+ excluded: true,
100
+ reason: "pickPendingNonRBF",
101
+ };
102
+ }
98
103
  if (
99
104
  utxoStrategy.excludeUTXOs.some(
100
105
  (u) => u.hash === utxo.hash && u.outputIndex === utxo.outputIndex
@@ -254,7 +254,12 @@ const bitcoinLikeMutations = ({
254
254
  },
255
255
  recoverBadTransactionStatus,
256
256
  test: ({ account }) => {
257
- expect(account.balance.toString()).toBe("0");
257
+ expect(
258
+ account.bitcoinResources?.utxos
259
+ .filter((u) => u.blockHeight) // Exclude pending UTXOs
260
+ .reduce((p, c) => p.plus(c.value), new BigNumber(0))
261
+ .toString()
262
+ ).toBe("0");
258
263
  },
259
264
  },
260
265
  ];
@@ -1,7 +1,7 @@
1
1
  import type { DatasetTest } from "../../types";
2
2
  import type { Transaction } from "./types";
3
3
  import bitcoin_cash from "./datasets/bitcoin_cash";
4
- import bitcoin_gold from "./datasets/bitcoin_gold";
4
+ // import bitcoin_gold from "./datasets/bitcoin_gold";
5
5
  import bitcoin from "./datasets/bitcoin";
6
6
  import dash from "./datasets/dash";
7
7
  // import decred from "./datasets/decred";
@@ -22,7 +22,7 @@ const dataset: DatasetTest<Transaction> = {
22
22
  currencies: {
23
23
  bitcoin,
24
24
  bitcoin_cash,
25
- bitcoin_gold,
25
+ // bitcoin_gold,
26
26
  dash,
27
27
  // decred, // BACK-2443
28
28
  digibyte,
@@ -27,6 +27,9 @@ export function fallbackValidateAddress(address: string): boolean {
27
27
  class Base implements ICrypto {
28
28
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
29
  network: any;
30
+ protected static publickeyCache = {}; // xpub + account + index to publicKey
31
+ protected static addressCache = {}; // derivationMode + xpub + account + index to address
32
+ protected static bech32Cache = {}; // xpub to bech32 interface
30
33
 
31
34
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
35
  constructor({ network }: { network: any }) {
@@ -37,9 +40,26 @@ class Base implements ICrypto {
37
40
  }
38
41
 
39
42
  protected getPubkeyAt(xpub: string, account: number, index: number): Buffer {
40
- return bip32.fromBase58(xpub, this.network).derive(account).derive(index)
41
- .publicKey;
43
+ if (!Base.bech32Cache[xpub]) {
44
+ Base.bech32Cache[xpub] = bip32.fromBase58(xpub, this.network);
45
+ }
46
+ if (Base.publickeyCache[`${xpub}-${account}-${index}`]) {
47
+ return Base.publickeyCache[`${xpub}-${account}-${index}`];
48
+ }
49
+ if (Base.publickeyCache[`${xpub}-${account}`]) {
50
+ const publicKey =
51
+ Base.publickeyCache[`${xpub}-${account}`].derive(index).publicKey;
52
+ Base.publickeyCache[`${xpub}-${account}-${index}`] = publicKey;
53
+ return publicKey;
54
+ }
55
+ Base.publickeyCache[`${xpub}-${account}`] =
56
+ Base.bech32Cache[xpub].derive(account);
57
+ const publicKey =
58
+ Base.publickeyCache[`${xpub}-${account}`].derive(index).publicKey;
59
+ Base.publickeyCache[`${xpub}-${account}-${index}`] = publicKey;
60
+ return publicKey;
42
61
  }
62
+
43
63
  // derive legacy address at account and index positions
44
64
  getLegacyAddress(xpub: string, account: number, index: number): string {
45
65
  const { address } = bjs.payments.p2pkh({
@@ -78,16 +98,26 @@ class Base implements ICrypto {
78
98
  account: number,
79
99
  index: number
80
100
  ): string {
101
+ if (Base.addressCache[`${derivationMode}-${xpub}-${account}-${index}`]) {
102
+ return Base.addressCache[`${derivationMode}-${xpub}-${account}-${index}`];
103
+ }
81
104
  switch (derivationMode) {
82
105
  case DerivationModes.LEGACY:
83
- return this.getLegacyAddress(xpub, account, index);
106
+ Base.addressCache[`${derivationMode}-${xpub}-${account}-${index}`] =
107
+ this.getLegacyAddress(xpub, account, index);
108
+ break;
84
109
  case DerivationModes.SEGWIT:
85
- return this.getSegWitAddress(xpub, account, index);
110
+ Base.addressCache[`${derivationMode}-${xpub}-${account}-${index}`] =
111
+ this.getSegWitAddress(xpub, account, index);
112
+ break;
86
113
  case DerivationModes.NATIVE_SEGWIT:
87
- return this.getNativeSegWitAddress(xpub, account, index);
114
+ Base.addressCache[`${derivationMode}-${xpub}-${account}-${index}`] =
115
+ this.getNativeSegWitAddress(xpub, account, index);
116
+ break;
88
117
  default:
89
118
  throw new Error(`Invalid derivation Mode: ${derivationMode}`);
90
119
  }
120
+ return Base.addressCache[`${derivationMode}-${xpub}-${account}-${index}`];
91
121
  }
92
122
 
93
123
  // infer address type from its syntax
@@ -119,6 +149,11 @@ class Base implements ICrypto {
119
149
  (res[0] === this.network.pubKeyHash || res[0] === this.network.scriptHash)
120
150
  );
121
151
  }
152
+
153
+ // eslint-disable-next-line
154
+ isTaprootAddress(address: string): boolean {
155
+ return false;
156
+ }
122
157
  }
123
158
 
124
159
  export default Base;
@@ -124,9 +124,16 @@ class Bitcoin extends Base {
124
124
  account: number,
125
125
  index: number
126
126
  ): string {
127
+ if (Base.addressCache[`${derivationMode}-${xpub}-${account}-${index}`]) {
128
+ return Base.addressCache[`${derivationMode}-${xpub}-${account}-${index}`];
129
+ }
127
130
  switch (derivationMode) {
128
131
  case DerivationModes.TAPROOT:
129
- return this.getTaprootAddress(xpub, account, index);
132
+ Base.addressCache[`${derivationMode}-${xpub}-${account}-${index}`] =
133
+ this.getTaprootAddress(xpub, account, index);
134
+ return Base.addressCache[
135
+ `${derivationMode}-${xpub}-${account}-${index}`
136
+ ];
130
137
  default:
131
138
  return super.getAddress(derivationMode, xpub, account, index);
132
139
  }
@@ -202,6 +209,19 @@ class Bitcoin extends Base {
202
209
  // Create address
203
210
  return toBech32(outputSchnorrKey, 1, this.network.bech32);
204
211
  }
212
+
213
+ isTaprootAddress(address: string): boolean {
214
+ // This prefix check is to avoid returning false in cases where a valid base58 address also happens
215
+ // to be a valid bech32(m) string (but invalid segwit address).
216
+ if (address.toLowerCase().startsWith(`${this.network.bech32}1`)) {
217
+ try {
218
+ bjs.address.fromBech32(address);
219
+ } catch {
220
+ return true;
221
+ }
222
+ }
223
+ return false;
224
+ }
205
225
  }
206
226
 
207
227
  export default Bitcoin;
@@ -70,6 +70,11 @@ class BitcoinCash implements ICrypto {
70
70
  validateAddress(address: string): boolean {
71
71
  return bchaddr.isValidAddress(address);
72
72
  }
73
+
74
+ // eslint-disable-next-line
75
+ isTaprootAddress(address: string): boolean {
76
+ return false;
77
+ }
73
78
  }
74
79
 
75
80
  export default BitcoinCash;
@@ -11,6 +11,7 @@ export interface ICrypto {
11
11
 
12
12
  toOutputScript(address: string): Buffer;
13
13
  validateAddress(address: string): boolean;
14
+ isTaprootAddress(address: string): boolean;
14
15
  }
15
16
 
16
17
  export type Currency =
@@ -74,6 +74,11 @@ class ZCash implements ICrypto {
74
74
  validateAddress(address: string): boolean {
75
75
  return zec.Address.isValid(address, "livenet");
76
76
  }
77
+
78
+ // eslint-disable-next-line
79
+ isTaprootAddress(address: string): boolean {
80
+ return false;
81
+ }
77
82
  }
78
83
 
79
84
  export default ZCash;
@@ -102,6 +102,11 @@ class Zen implements ICrypto {
102
102
  (res[1] === 0x89 || res[1] === 0x96)
103
103
  );
104
104
  }
105
+
106
+ // eslint-disable-next-line
107
+ isTaprootAddress(address: string): boolean {
108
+ return false;
109
+ }
105
110
  }
106
111
 
107
112
  export default Zen;
@@ -285,12 +285,7 @@ class BitcoinLikeExplorer extends EventEmitter implements IExplorer {
285
285
 
286
286
  // faster than mapping
287
287
  res.txs.forEach((tx) => {
288
- if (!tx.block) {
289
- return;
290
- }
291
-
292
288
  this.hydrateTx(address, tx);
293
-
294
289
  hydratedTxs.push(tx);
295
290
  });
296
291
 
@@ -11,7 +11,7 @@ import { TX, Input, Output } from "./storage/types";
11
11
  import { CoinSelect } from "./pickingstrategies/CoinSelect";
12
12
  import { DeepFirst } from "./pickingstrategies/DeepFirst";
13
13
  import { Merge } from "./pickingstrategies/Merge";
14
- import { isValidAddress } from "./utils";
14
+ import { isValidAddress, isTaprootAddress } from "./utils";
15
15
 
16
16
  import type { Account as WalletAccount } from "./account";
17
17
  import type { Account as LiveAccount } from "./../../../types";
@@ -32,16 +32,16 @@ export {
32
32
  DeepFirst,
33
33
  Merge,
34
34
  isValidAddress,
35
+ isTaprootAddress,
35
36
  Currency,
36
37
  };
37
38
 
38
39
  let wallet: BitcoinLikeWallet | null = null;
39
40
 
40
41
  export const getWalletAccount = (account: LiveAccount): WalletAccount => {
41
- if (account.id.startsWith("libcore")) throw new AccountNeedResync();
42
42
  const walletAccount = account.bitcoinResources?.walletAccount;
43
- if (!walletAccount) {
44
- throw new Error("bitcoin wallet account expected");
43
+ if (account.id.startsWith("libcore") || !walletAccount) {
44
+ throw new AccountNeedResync();
45
45
  }
46
46
  return walletAccount;
47
47
  };
@@ -34,7 +34,7 @@ export class CoinSelect extends PickingStrategy {
34
34
  const TOTAL_TRIES = 100000;
35
35
  log("picking strategy", "utxos", unspentUtxos);
36
36
  // Compute cost of change
37
- const fixedSize = utils.estimateTxSize(
37
+ const fixedSize = utils.accurateTxSize(
38
38
  0,
39
39
  0,
40
40
  this.crypto,
@@ -42,10 +42,10 @@ export class CoinSelect extends PickingStrategy {
42
42
  );
43
43
  // Size of only 1 output (without fixed size)
44
44
  const oneOutputSize =
45
- utils.estimateTxSize(0, 1, this.crypto, this.derivationMode) - fixedSize;
45
+ utils.accurateTxSize(0, 1, this.crypto, this.derivationMode) - fixedSize;
46
46
  // Size 1 signed UTXO (signed input)
47
47
  const oneInputSize =
48
- utils.estimateTxSize(1, 0, this.crypto, this.derivationMode) - fixedSize;
48
+ utils.accurateTxSize(1, 0, this.crypto, this.derivationMode) - fixedSize;
49
49
 
50
50
  // Calculate effective value of outputs
51
51
  let currentAvailableValue = 0;
@@ -33,7 +33,7 @@ export class DeepFirst extends PickingStrategy {
33
33
 
34
34
  unspentUtxos = sortBy(unspentUtxos, "block_height");
35
35
  // https://metamug.com/article/security/bitcoin-transaction-fee-satoshi-per-byte.html
36
- const txSizeNoInput = utils.estimateTxSize(
36
+ const txSizeNoInput = utils.accurateTxSize(
37
37
  0,
38
38
  nbOutputsWithoutChange,
39
39
  this.crypto,
@@ -41,12 +41,12 @@ export class DeepFirst extends PickingStrategy {
41
41
  );
42
42
  let fee = txSizeNoInput * feePerByte;
43
43
  const sizePerInput =
44
- utils.estimateTxSize(1, 0, this.crypto, this.derivationMode) -
45
- utils.estimateTxSize(0, 0, this.crypto, this.derivationMode);
44
+ utils.accurateTxSize(1, 0, this.crypto, this.derivationMode) -
45
+ utils.accurateTxSize(0, 0, this.crypto, this.derivationMode);
46
46
 
47
47
  const sizePerOutput =
48
- utils.estimateTxSize(0, 1, this.crypto, this.derivationMode) -
49
- utils.estimateTxSize(0, 0, this.crypto, this.derivationMode);
48
+ utils.accurateTxSize(0, 1, this.crypto, this.derivationMode) -
49
+ utils.accurateTxSize(0, 0, this.crypto, this.derivationMode);
50
50
 
51
51
  let total = new BigNumber(0);
52
52
  const unspentUtxoSelected: Output[] = [];
@@ -32,16 +32,16 @@ export class Merge extends PickingStrategy {
32
32
  );
33
33
 
34
34
  const sizePerInput =
35
- utils.estimateTxSize(1, 0, this.crypto, this.derivationMode) -
36
- utils.estimateTxSize(0, 0, this.crypto, this.derivationMode);
35
+ utils.accurateTxSize(1, 0, this.crypto, this.derivationMode) -
36
+ utils.accurateTxSize(0, 0, this.crypto, this.derivationMode);
37
37
 
38
38
  const sizePerOutput =
39
- utils.estimateTxSize(0, 1, this.crypto, this.derivationMode) -
40
- utils.estimateTxSize(0, 0, this.crypto, this.derivationMode);
39
+ utils.accurateTxSize(0, 1, this.crypto, this.derivationMode) -
40
+ utils.accurateTxSize(0, 0, this.crypto, this.derivationMode);
41
41
 
42
42
  unspentUtxos = sortBy(unspentUtxos, (utxo) => parseInt(utxo.value, 10));
43
43
  // https://metamug.com/article/security/bitcoin-transaction-fee-satoshi-per-byte.html
44
- const txSizeNoInput = utils.estimateTxSize(
44
+ const txSizeNoInput = utils.accurateTxSize(
45
45
  0,
46
46
  nbOutputsWithoutChange,
47
47
  this.crypto,
@@ -83,16 +83,16 @@ export function byteSize(count: number) {
83
83
  return 1;
84
84
  }
85
85
  if (count <= 0xffff) {
86
- return 2;
86
+ return 3;
87
87
  }
88
88
  if (count <= 0xffffffff) {
89
- return 4;
89
+ return 5;
90
90
  }
91
- return 8;
91
+ return 9;
92
92
  }
93
93
 
94
94
  // refer to https://github.com/LedgerHQ/lib-ledger-core/blob/fc9d762b83fc2b269d072b662065747a64ab2816/core/src/wallet/bitcoin/api_impl/BitcoinLikeTransactionApi.cpp#L217
95
- export function estimateTxSize(
95
+ export function accurateTxSize(
96
96
  inputCount: number,
97
97
  outputCount: number,
98
98
  currency: ICrypto,
@@ -106,11 +106,15 @@ export function estimateTxSize(
106
106
  fixedSize += byteSize(inputCount); // Number of inputs
107
107
  fixedSize += byteSize(outputCount); // Number of outputs
108
108
  fixedSize += 4; // Timelock
109
+ if (derivationMode !== DerivationModes.LEGACY) {
110
+ fixedSize += 0.5; // Segwit marker & segwit flag
111
+ }
109
112
  // refer to https://medium.com/coinmonks/on-bitcoin-transaction-sizes-part-2-9445373d17f4
110
113
  // and https://bitcoin.stackexchange.com/questions/96017/what-are-the-sizes-of-single-sig-and-2-of-3-multisig-taproot-inputs
114
+ // and https://bitcoinops.org/en/tools/calc-size/
111
115
  if (derivationMode === DerivationModes.TAPROOT) {
112
- txSize = fixedSize + 57.5 * inputCount + 43 * outputCount;
113
- return Math.ceil(txSize);
116
+ txSize = fixedSize + 57.75 * inputCount + 43 * outputCount;
117
+ return txSize;
114
118
  }
115
119
  const isSegwit =
116
120
  derivationMode === DerivationModes.NATIVE_SEGWIT ||
@@ -120,14 +124,25 @@ export function estimateTxSize(
120
124
  // P2SH: 32 PrevTxHash + 4 Index + 23 scriptPubKey + 4 sequence
121
125
  const isNativeSegwit = derivationMode === DerivationModes.NATIVE_SEGWIT;
122
126
  const inputSize = isNativeSegwit ? 41 : 63;
123
- const noWitness = fixedSize + inputSize * inputCount + 34 * outputCount;
127
+ const noWitness = fixedSize + inputSize * inputCount + 43 * outputCount; //43 is the biggest output(taproot output) size, we use the biggest one for safety
124
128
  // Include flag and marker size (one byte each)
125
129
  const witnessSize = noWitness + 108 * inputCount + 2;
126
130
  txSize = (noWitness * 3 + witnessSize) / 4;
127
131
  } else {
128
- txSize = fixedSize + 148 * inputCount + 34 * outputCount;
132
+ txSize = fixedSize + 148 * inputCount + 43 * outputCount;
129
133
  }
130
- return Math.ceil(txSize); // We don't allow floating value
134
+ return txSize;
135
+ }
136
+
137
+ export function estimateTxSize(
138
+ inputCount: number,
139
+ outputCount: number,
140
+ currency: ICrypto,
141
+ derivationMode: string
142
+ ) {
143
+ return Math.ceil(
144
+ accurateTxSize(inputCount, outputCount, currency, derivationMode)
145
+ ); // We don't allow floating value
131
146
  }
132
147
 
133
148
  // refer to https://github.com/LedgerHQ/lib-ledger-core/blob/fc9d762b83fc2b269d072b662065747a64ab2816/core/src/wallet/bitcoin/api_impl/BitcoinLikeTransactionApi.cpp#L253
@@ -156,3 +171,13 @@ export function isValidAddress(address: string, currency?: Currency) {
156
171
  const crypto = cryptoFactory(currency);
157
172
  return crypto.validateAddress(address);
158
173
  }
174
+
175
+ export function isTaprootAddress(address: string, currency?: Currency) {
176
+ if (currency === "bitcoin") {
177
+ return cryptoFactory("bitcoin").isTaprootAddress(address);
178
+ } else if (currency === "bitcoin_testnet") {
179
+ return cryptoFactory("bitcoin_testnet").isTaprootAddress(address);
180
+ } else {
181
+ return false;
182
+ }
183
+ }
@@ -133,6 +133,7 @@ class BitcoinLikeWallet {
133
133
  )
134
134
  );
135
135
  let balance = new BigNumber(0);
136
+ let availableUtxoCount = 0;
136
137
  log("btcwallet", "estimateAccountMaxSpendable utxos", utxos);
137
138
  utxos.forEach((utxo) => {
138
139
  if (
@@ -144,6 +145,7 @@ class BitcoinLikeWallet {
144
145
  ) {
145
146
  if ((pickUnconfirmedRBF && utxo.rbf) || utxo.block_height !== null) {
146
147
  balance = balance.plus(utxo.value);
148
+ availableUtxoCount += 1;
147
149
  }
148
150
  }
149
151
  });
@@ -151,7 +153,7 @@ class BitcoinLikeWallet {
151
153
  const fees =
152
154
  feePerByte *
153
155
  utils.estimateTxSize(
154
- utxos.length,
156
+ availableUtxoCount,
155
157
  1,
156
158
  account.xpub.crypto,
157
159
  account.xpub.derivationMode
@@ -263,7 +265,7 @@ class BitcoinLikeWallet {
263
265
  return [
264
266
  btc.splitTransaction(
265
267
  i.txHex,
266
- segwit,
268
+ true,
267
269
  false, // FIXME hasTimestamp needed for LL-7539
268
270
  hasExtraData,
269
271
  additionals