@ledgerhq/live-common 21.4.0 → 21.4.1-btcjs.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 (159) hide show
  1. package/lib/__tests__/families/bitcoin/logic.d.ts +2 -0
  2. package/lib/__tests__/families/bitcoin/logic.d.ts.map +1 -0
  3. package/lib/__tests__/families/bitcoin/logic.js +39 -0
  4. package/lib/__tests__/families/bitcoin/logic.js.map +1 -0
  5. package/lib/__tests__/test-helpers/mockDevice.d.ts.map +1 -1
  6. package/lib/__tests__/test-helpers/mockDevice.js +0 -1
  7. package/lib/__tests__/test-helpers/mockDevice.js.map +1 -1
  8. package/lib/account/addAccounts.d.ts.map +1 -1
  9. package/lib/account/addAccounts.js +2 -1
  10. package/lib/account/addAccounts.js.map +1 -1
  11. package/lib/account/support.d.ts.map +1 -1
  12. package/lib/account/support.js +24 -1
  13. package/lib/account/support.js.map +1 -1
  14. package/lib/api/explorerConfig/index.js +15 -15
  15. package/lib/bridge/jsHelpers.d.ts +6 -1
  16. package/lib/bridge/jsHelpers.d.ts.map +1 -1
  17. package/lib/bridge/jsHelpers.js +11 -3
  18. package/lib/bridge/jsHelpers.js.map +1 -1
  19. package/lib/families/bitcoin/account.d.ts.map +1 -1
  20. package/lib/families/bitcoin/account.js +3 -2
  21. package/lib/families/bitcoin/account.js.map +1 -1
  22. package/lib/families/bitcoin/bridge/js.d.ts +8 -0
  23. package/lib/families/bitcoin/bridge/js.d.ts.map +1 -0
  24. package/lib/families/bitcoin/bridge/js.js +149 -0
  25. package/lib/families/bitcoin/bridge/js.js.map +1 -0
  26. package/lib/families/bitcoin/cache.d.ts +28 -0
  27. package/lib/families/bitcoin/cache.d.ts.map +1 -0
  28. package/lib/families/bitcoin/cache.js +109 -0
  29. package/lib/families/bitcoin/cache.js.map +1 -0
  30. package/lib/families/bitcoin/datasets/bitcoin.js +6 -6
  31. package/lib/families/bitcoin/datasets/bitcoin.js.map +1 -1
  32. package/lib/families/bitcoin/datasets/litecoin.js +3 -3
  33. package/lib/families/bitcoin/datasets/litecoin.js.map +1 -1
  34. package/lib/families/bitcoin/getAccountNetworkInfo.d.ts +6 -0
  35. package/lib/families/bitcoin/getAccountNetworkInfo.d.ts.map +1 -0
  36. package/lib/families/bitcoin/getAccountNetworkInfo.js +124 -0
  37. package/lib/families/bitcoin/getAccountNetworkInfo.js.map +1 -0
  38. package/lib/families/bitcoin/getAccountNetworkInfo.test.d.ts +2 -0
  39. package/lib/families/bitcoin/getAccountNetworkInfo.test.d.ts.map +1 -0
  40. package/lib/families/bitcoin/getAccountNetworkInfo.test.js +71 -0
  41. package/lib/families/bitcoin/getAccountNetworkInfo.test.js.map +1 -0
  42. package/lib/families/bitcoin/js-broadcast.d.ts +11 -0
  43. package/lib/families/bitcoin/js-broadcast.d.ts.map +1 -0
  44. package/lib/families/bitcoin/js-broadcast.js +80 -0
  45. package/lib/families/bitcoin/js-broadcast.js.map +1 -0
  46. package/lib/families/bitcoin/js-buildTransaction.d.ts +5 -0
  47. package/lib/families/bitcoin/js-buildTransaction.d.ts.map +1 -0
  48. package/lib/families/bitcoin/js-buildTransaction.js +106 -0
  49. package/lib/families/bitcoin/js-buildTransaction.js.map +1 -0
  50. package/lib/families/bitcoin/js-createTransaction.d.ts +9 -0
  51. package/lib/families/bitcoin/js-createTransaction.d.ts.map +1 -0
  52. package/lib/families/bitcoin/js-createTransaction.js +27 -0
  53. package/lib/families/bitcoin/js-createTransaction.js.map +1 -0
  54. package/lib/families/bitcoin/js-estimateMaxSpendable.d.ts +15 -0
  55. package/lib/families/bitcoin/js-estimateMaxSpendable.d.ts.map +1 -0
  56. package/lib/families/bitcoin/js-estimateMaxSpendable.js +95 -0
  57. package/lib/families/bitcoin/js-estimateMaxSpendable.js.map +1 -0
  58. package/lib/families/bitcoin/js-getFeesForTransaction.d.ts +26 -0
  59. package/lib/families/bitcoin/js-getFeesForTransaction.d.ts.map +1 -0
  60. package/lib/families/bitcoin/js-getFeesForTransaction.js +100 -0
  61. package/lib/families/bitcoin/js-getFeesForTransaction.js.map +1 -0
  62. package/lib/families/bitcoin/js-getTransactionStatus.d.ts +5 -0
  63. package/lib/families/bitcoin/js-getTransactionStatus.d.ts.map +1 -0
  64. package/lib/families/bitcoin/js-getTransactionStatus.js +131 -0
  65. package/lib/families/bitcoin/js-getTransactionStatus.js.map +1 -0
  66. package/lib/families/bitcoin/js-prepareTransaction.d.ts +5 -0
  67. package/lib/families/bitcoin/js-prepareTransaction.d.ts.map +1 -0
  68. package/lib/families/bitcoin/js-prepareTransaction.js +80 -0
  69. package/lib/families/bitcoin/js-prepareTransaction.js.map +1 -0
  70. package/lib/families/bitcoin/js-signOperation.d.ts +10 -0
  71. package/lib/families/bitcoin/js-signOperation.d.ts.map +1 -0
  72. package/lib/families/bitcoin/js-signOperation.js +212 -0
  73. package/lib/families/bitcoin/js-signOperation.js.map +1 -0
  74. package/lib/families/bitcoin/js-synchronisation.d.ts +10 -0
  75. package/lib/families/bitcoin/js-synchronisation.d.ts.map +1 -0
  76. package/lib/families/bitcoin/js-synchronisation.js +411 -0
  77. package/lib/families/bitcoin/js-synchronisation.js.map +1 -0
  78. package/lib/families/bitcoin/logic.d.ts +34 -0
  79. package/lib/families/bitcoin/logic.d.ts.map +1 -0
  80. package/lib/families/bitcoin/logic.js +290 -0
  81. package/lib/families/bitcoin/logic.js.map +1 -0
  82. package/lib/families/bitcoin/networks.d.ts +3 -0
  83. package/lib/families/bitcoin/networks.d.ts.map +1 -0
  84. package/lib/families/bitcoin/networks.js +379 -0
  85. package/lib/families/bitcoin/networks.js.map +1 -0
  86. package/lib/families/bitcoin/serialization.d.ts +2 -2
  87. package/lib/families/bitcoin/serialization.d.ts.map +1 -1
  88. package/lib/families/bitcoin/serialization.js +8 -4
  89. package/lib/families/bitcoin/serialization.js.map +1 -1
  90. package/lib/families/bitcoin/specs.d.ts.map +1 -1
  91. package/lib/families/bitcoin/specs.js +8 -5
  92. package/lib/families/bitcoin/specs.js.map +1 -1
  93. package/lib/families/bitcoin/speculos-deviceActions.js +2 -2
  94. package/lib/families/bitcoin/speculos-deviceActions.js.map +1 -1
  95. package/lib/families/bitcoin/test-dataset.js +1 -1
  96. package/lib/families/bitcoin/test-dataset.js.map +1 -1
  97. package/lib/families/bitcoin/transaction.d.ts +3 -3
  98. package/lib/families/bitcoin/transaction.d.ts.map +1 -1
  99. package/lib/families/bitcoin/transaction.js +64 -61
  100. package/lib/families/bitcoin/transaction.js.map +1 -1
  101. package/lib/families/bitcoin/types.d.ts +66 -36
  102. package/lib/families/bitcoin/types.d.ts.map +1 -1
  103. package/lib/families/bitcoin/types.js +16 -1
  104. package/lib/families/bitcoin/types.js.map +1 -1
  105. package/lib/families/bitcoin/wallet.d.ts +6 -0
  106. package/lib/families/bitcoin/wallet.d.ts.map +1 -0
  107. package/lib/families/bitcoin/wallet.js +66 -0
  108. package/lib/families/bitcoin/wallet.js.map +1 -0
  109. package/lib/generated/bridge/js.d.ts +4 -0
  110. package/lib/generated/bridge/js.d.ts.map +1 -1
  111. package/lib/generated/bridge/js.js +20 -18
  112. package/lib/generated/bridge/js.js.map +1 -1
  113. package/lib/migrations/accounts-dataset.d.ts.map +1 -1
  114. package/lib/migrations/accounts-dataset.js +4 -2
  115. package/lib/migrations/accounts-dataset.js.map +1 -1
  116. package/lib/migrations/accounts.d.ts.map +1 -1
  117. package/lib/migrations/accounts.js +17 -0
  118. package/lib/migrations/accounts.js.map +1 -1
  119. package/lib/mock/account.d.ts.map +1 -1
  120. package/lib/mock/account.js +2 -1
  121. package/lib/mock/account.js.map +1 -1
  122. package/package.json +6 -6
  123. package/src/__tests__/__snapshots__/all.libcore.ts.snap +0 -125
  124. package/src/__tests__/families/bitcoin/logic.ts +40 -0
  125. package/src/__tests__/test-helpers/mockDevice.ts +0 -1
  126. package/src/account/addAccounts.ts +2 -1
  127. package/src/account/support.ts +24 -1
  128. package/src/api/explorerConfig/index.ts +15 -15
  129. package/src/bridge/jsHelpers.ts +22 -2
  130. package/src/families/bitcoin/account.ts +2 -1
  131. package/src/families/bitcoin/bridge/js.ts +76 -0
  132. package/src/families/bitcoin/cache.ts +76 -0
  133. package/src/families/bitcoin/datasets/bitcoin.ts +6 -6
  134. package/src/families/bitcoin/datasets/litecoin.ts +6 -6
  135. package/src/families/bitcoin/getAccountNetworkInfo.test.ts +27 -0
  136. package/src/families/bitcoin/getAccountNetworkInfo.ts +61 -0
  137. package/src/families/bitcoin/js-broadcast.ts +30 -0
  138. package/src/families/bitcoin/js-buildTransaction.ts +57 -0
  139. package/src/families/bitcoin/js-createTransaction.ts +27 -0
  140. package/src/families/bitcoin/js-estimateMaxSpendable.ts +36 -0
  141. package/src/families/bitcoin/js-getFeesForTransaction.ts +59 -0
  142. package/src/families/bitcoin/js-getTransactionStatus.ts +118 -0
  143. package/src/families/bitcoin/js-prepareTransaction.ts +35 -0
  144. package/src/families/bitcoin/js-signOperation.ts +164 -0
  145. package/src/families/bitcoin/js-synchronisation.ts +362 -0
  146. package/src/families/bitcoin/logic.ts +255 -0
  147. package/src/families/bitcoin/networks.ts +360 -0
  148. package/src/families/bitcoin/serialization.ts +6 -0
  149. package/src/families/bitcoin/specs.ts +5 -2
  150. package/src/families/bitcoin/speculos-deviceActions.ts +1 -1
  151. package/src/families/bitcoin/test-dataset.ts +1 -1
  152. package/src/families/bitcoin/transaction.ts +98 -93
  153. package/src/families/bitcoin/types.ts +95 -37
  154. package/src/families/bitcoin/wallet.ts +21 -0
  155. package/src/families/ethereum/modules/__snapshots__/compound.resilience.test.ts.snap +50 -9
  156. package/src/generated/bridge/js.ts +3 -0
  157. package/src/migrations/accounts-dataset.ts +3 -0
  158. package/src/migrations/accounts.ts +18 -0
  159. package/src/mock/account.ts +2 -0
@@ -0,0 +1,76 @@
1
+ import type { AccountBridge, CurrencyBridge } from "../../../types/bridge";
2
+ import type { Transaction } from "../types";
3
+ import { sync, scanAccounts } from "../js-synchronisation";
4
+ import createTransaction from "../js-createTransaction";
5
+ import prepareTransaction from "../js-prepareTransaction";
6
+ import getTransactionStatus from "../js-getTransactionStatus";
7
+ import estimateMaxSpendable from "../js-estimateMaxSpendable";
8
+ import signOperation from "../js-signOperation";
9
+ import broadcast from "../js-broadcast";
10
+ import { calculateFees } from "./../cache";
11
+ import { perCoinLogic } from "../logic";
12
+ import { makeAccountBridgeReceive } from "../../../bridge/jsHelpers";
13
+ import * as explorerConfigAPI from "../../../api/explorerConfig";
14
+ const receive = makeAccountBridgeReceive({
15
+ injectGetAddressParams: (account) => {
16
+ const perCoin = perCoinLogic[account.currency.id];
17
+
18
+ if (perCoin && perCoin.injectGetAddressParams) {
19
+ return perCoin.injectGetAddressParams(account);
20
+ }
21
+ },
22
+ });
23
+
24
+ const updateTransaction = (t, patch) => {
25
+ const updatedT = { ...t, ...patch };
26
+
27
+ if (updatedT.recipient.toLowerCase().indexOf("bc1") === 0) {
28
+ updatedT.recipient = updatedT.recipient.toLowerCase();
29
+ }
30
+
31
+ return updatedT;
32
+ };
33
+
34
+ const preload = async () => {
35
+ const explorerConfig = await explorerConfigAPI.preload();
36
+ return {
37
+ explorerConfig,
38
+ };
39
+ };
40
+
41
+ const hydrate = (maybeConfig: any) => {
42
+ if (
43
+ typeof maybeConfig === "object" &&
44
+ maybeConfig &&
45
+ maybeConfig.explorerConfig
46
+ ) {
47
+ explorerConfigAPI.hydrate(maybeConfig.explorerConfig);
48
+ }
49
+ };
50
+
51
+ const currencyBridge: CurrencyBridge = {
52
+ scanAccounts,
53
+ preload,
54
+ hydrate,
55
+ };
56
+ const accountBridge: AccountBridge<Transaction> = {
57
+ estimateMaxSpendable,
58
+ createTransaction,
59
+ prepareTransaction,
60
+ updateTransaction,
61
+ getTransactionStatus,
62
+ receive,
63
+ sync,
64
+ signOperation,
65
+ broadcast: async ({ account, signedOperation }) => {
66
+ calculateFees.reset();
67
+ return broadcast({
68
+ account,
69
+ signedOperation,
70
+ });
71
+ },
72
+ };
73
+ export default {
74
+ currencyBridge,
75
+ accountBridge,
76
+ };
@@ -0,0 +1,76 @@
1
+ import { RecipientRequired } from "@ledgerhq/errors";
2
+ import { makeLRUCache } from "./../../cache";
3
+ import type { Account, CryptoCurrency } from "./../../types";
4
+ import type { Transaction } from "./types";
5
+ import getFeesForTransaction from "./js-getFeesForTransaction";
6
+ import { isValidRecipient } from "./logic";
7
+
8
+ const getCacheKeyForCalculateFees = ({
9
+ a,
10
+ t,
11
+ }: {
12
+ a: Account;
13
+ t: Transaction;
14
+ }) =>
15
+ `${a.id}_${a.blockHeight || 0}_${t.amount.toString()}_${String(
16
+ t.useAllAmount
17
+ )}_${t.recipient}_${t.feePerByte ? t.feePerByte.toString() : ""}_${
18
+ t.utxoStrategy.pickUnconfirmedRBF ? 1 : 0
19
+ }_${t.utxoStrategy.strategy}_${String(t.rbf)}_${t.utxoStrategy.excludeUTXOs
20
+ .map(({ hash, outputIndex }) => `${hash}@${outputIndex}`)
21
+ .join("+")}`;
22
+
23
+ export const calculateFees = makeLRUCache(
24
+ async ({
25
+ account,
26
+ transaction,
27
+ }: {
28
+ account: Account;
29
+ transaction: Transaction;
30
+ }) =>
31
+ getFeesForTransaction({
32
+ account,
33
+ transaction,
34
+ }),
35
+ ({ account, transaction }) =>
36
+ getCacheKeyForCalculateFees({
37
+ a: account,
38
+ t: transaction,
39
+ }),
40
+ {
41
+ maxAge: 5 * 60 * 1000, // 5 minutes
42
+ }
43
+ );
44
+ export const validateRecipient: (
45
+ arg0: CryptoCurrency,
46
+ arg1: string | null | undefined
47
+ ) => Promise<{
48
+ recipientError: Error | null | undefined;
49
+ recipientWarning: Error | null | undefined;
50
+ }> = makeLRUCache(
51
+ async (currency, recipient) => {
52
+ if (!recipient) {
53
+ return {
54
+ recipientError: new RecipientRequired(""),
55
+ recipientWarning: null,
56
+ };
57
+ }
58
+
59
+ try {
60
+ const recipientWarning = await isValidRecipient({
61
+ currency,
62
+ recipient,
63
+ });
64
+ return {
65
+ recipientError: null,
66
+ recipientWarning,
67
+ };
68
+ } catch (recipientError) {
69
+ return {
70
+ recipientError,
71
+ recipientWarning: null,
72
+ };
73
+ }
74
+ },
75
+ (currency, recipient) => `${currency.id}_${recipient || ""}`
76
+ );
@@ -50,8 +50,8 @@ const dataset: CurrenciesData<Transaction> = {
50
50
  expectedStatus: {
51
51
  amount: new BigNumber("999"),
52
52
  // FIXME fee are reloaded?
53
- // estimatedFees: BigNumber("250"),
54
- // totalSpent: BigNumber("1249"),
53
+ // estimatedFees: new BigNumber("250"),
54
+ // totalSpent: new BigNumber("1249"),
55
55
  errors: {},
56
56
  warnings: {
57
57
  // feeTooHigh: new FeeTooHigh()
@@ -76,8 +76,8 @@ const dataset: CurrenciesData<Transaction> = {
76
76
  expectedStatus: {
77
77
  amount: new BigNumber("998"),
78
78
  // FIXME fee are reloaded?
79
- // estimatedFees: BigNumber("250"),
80
- // totalSpent: BigNumber("1248"),
79
+ // estimatedFees: new BigNumber("250"),
80
+ // totalSpent: new BigNumber("1248"),
81
81
  errors: {},
82
82
  warnings: {
83
83
  // feeTooHigh: new FeeTooHigh()
@@ -102,8 +102,8 @@ const dataset: CurrenciesData<Transaction> = {
102
102
  expectedStatus: {
103
103
  amount: new BigNumber("997"),
104
104
  // FIXME fee are reloaded?
105
- // estimatedFees: BigNumber("250"),
106
- // totalSpent: BigNumber("1247"),
105
+ // estimatedFees: new BigNumber("250"),
106
+ // totalSpent: new BigNumber("1247"),
107
107
  errors: {},
108
108
  warnings: {
109
109
  // feeTooHigh: new FeeTooHigh(),
@@ -50,8 +50,8 @@ const dataset: CurrenciesData<Transaction> = {
50
50
  expectedStatus: {
51
51
  errors: {},
52
52
  warnings: {},
53
- // estimatedFees: BigNumber("5694"),
54
- amount: new BigNumber("200000"), // totalSpent: BigNumber("205694")
53
+ // estimatedFees: new BigNumber("5694"),
54
+ amount: new BigNumber("200000"), // totalSpent: new BigNumber("205694")
55
55
  },
56
56
  },
57
57
  {
@@ -73,8 +73,8 @@ const dataset: CurrenciesData<Transaction> = {
73
73
  expectedStatus: {
74
74
  errors: {},
75
75
  warnings: {},
76
- // estimatedFees: BigNumber("5694"),
77
- amount: new BigNumber("147200"), // totalSpent: BigNumber("152894")
76
+ // estimatedFees: new BigNumber("5694"),
77
+ amount: new BigNumber("147200"), // totalSpent: new BigNumber("152894")
78
78
  },
79
79
  },
80
80
  {
@@ -96,8 +96,8 @@ const dataset: CurrenciesData<Transaction> = {
96
96
  expectedStatus: {
97
97
  errors: {},
98
98
  warnings: {},
99
- // estimatedFees: BigNumber("5694"),
100
- amount: new BigNumber("205273"), // totalSpent: BigNumber("210967")
99
+ // estimatedFees: new BigNumber("5694"),
100
+ amount: new BigNumber("205273"), // totalSpent: new BigNumber("210967")
101
101
  },
102
102
  },
103
103
  ],
@@ -0,0 +1,27 @@
1
+ import { BigNumber } from "bignumber.js";
2
+ import { avoidDups } from "./getAccountNetworkInfo";
3
+ test("avoidFeeDupsInFeePerByte [3,2,2] => [4,3,2]", async () => {
4
+ expect(
5
+ avoidDups([new BigNumber(3), new BigNumber(3), new BigNumber(2)])
6
+ ).toEqual([new BigNumber(4), new BigNumber(3), new BigNumber(2)]);
7
+ });
8
+ test("avoidFeeDupsInFeePerByte [2,2,1] => [3,2,1]", async () => {
9
+ expect(
10
+ avoidDups([new BigNumber(2), new BigNumber(2), new BigNumber(1)])
11
+ ).toEqual([new BigNumber(3), new BigNumber(2), new BigNumber(1)]);
12
+ });
13
+ test("avoidFeeDupsInFeePerByte [3,3,3] => [5,4,3]", async () => {
14
+ expect(
15
+ avoidDups([new BigNumber(3), new BigNumber(3), new BigNumber(3)])
16
+ ).toEqual([new BigNumber(5), new BigNumber(4), new BigNumber(3)]);
17
+ });
18
+ test("avoidFeeDupsInFeePerByte [3,2,1] => [3,2,1]", async () => {
19
+ expect(
20
+ avoidDups([new BigNumber(3), new BigNumber(2), new BigNumber(1)])
21
+ ).toEqual([new BigNumber(3), new BigNumber(2), new BigNumber(1)]);
22
+ });
23
+ test("avoidFeeDupsInFeePerByte [100,100,1] => [101,100,1]", async () => {
24
+ expect(
25
+ avoidDups([new BigNumber(100), new BigNumber(100), new BigNumber(1)])
26
+ ).toEqual([new BigNumber(101), new BigNumber(100), new BigNumber(1)]);
27
+ });
@@ -0,0 +1,61 @@
1
+ import flow from "lodash/flow";
2
+ import { BigNumber } from "bignumber.js";
3
+ import type { Account } from "../../types";
4
+ import type { NetworkInfo } from "./types";
5
+ import { getWalletAccount } from "./wallet";
6
+ const speeds = ["fast", "medium", "slow"];
7
+ export function avoidDups(nums: Array<BigNumber>): Array<BigNumber> {
8
+ nums = nums.slice(0);
9
+
10
+ for (let i = nums.length - 2; i >= 0; i--) {
11
+ if (nums[i + 1].gte(nums[i])) {
12
+ nums[i] = nums[i + 1].plus(1);
13
+ }
14
+ }
15
+
16
+ return nums;
17
+ }
18
+ export async function getAccountNetworkInfo(
19
+ account: Account
20
+ ): Promise<NetworkInfo> {
21
+ const walletAccount = await getWalletAccount(account);
22
+ const rawFees = await walletAccount.xpub.explorer.getFees();
23
+ // Convoluted logic to convert from:
24
+ // { "2": 2435, "3": 1241, "6": 1009, "last_updated": 1627973170 }
25
+ // to:
26
+ // { 2435, 1241, 1009 }
27
+ const feesPerByte = flow([
28
+ // Remove the 'last_updated' key
29
+ Object.entries,
30
+ (entries) => entries.filter(([key]) => !isNaN(key)),
31
+ // Reconstruct the array with only the values
32
+ Object.fromEntries,
33
+ Object.values,
34
+ // Safety against invalid data
35
+ (f) => f.filter((f) => f != null && typeof f === "number"),
36
+ // Normalize values
37
+ (f) =>
38
+ f.map((f) =>
39
+ new BigNumber(f).div(1000).integerValue(BigNumber.ROUND_CEIL)
40
+ ),
41
+ avoidDups,
42
+ ])(rawFees);
43
+
44
+ if (feesPerByte.length !== 3) {
45
+ throw new Error("cardinality of feesPerByte should be exactly 3");
46
+ }
47
+
48
+ const feeItems = {
49
+ items: feesPerByte.map((feePerByte, i) => ({
50
+ key: String(i),
51
+ speed: speeds[i],
52
+ feePerByte,
53
+ })),
54
+ defaultFeePerByte:
55
+ feesPerByte[Math.floor(feesPerByte.length / 2)] || new BigNumber(0),
56
+ };
57
+ return {
58
+ family: "bitcoin",
59
+ feeItems,
60
+ };
61
+ }
@@ -0,0 +1,30 @@
1
+ import { patchOperationWithHash } from "./../../operation";
2
+ import type { Account, Operation, SignedOperation } from "./../../types";
3
+ import wallet from "./wallet";
4
+
5
+ /**
6
+ * Broadcast a signed transaction
7
+ * @param {signature: string, operation: string} signedOperation
8
+ */
9
+ const broadcast = async ({
10
+ account,
11
+ signedOperation,
12
+ }: {
13
+ account: Account;
14
+ signedOperation: SignedOperation;
15
+ }): Promise<Operation> => {
16
+ const { signature, operation } = signedOperation;
17
+ const walletData = account.bitcoinResources?.serializedData;
18
+ if (!walletData) {
19
+ throw new Error("bitcoin wallet account expected");
20
+ }
21
+ const walletAccount = await wallet.importFromSerializedAccount(walletData);
22
+ try {
23
+ const hash = await wallet.broadcastTx(walletAccount, signature);
24
+ return patchOperationWithHash(operation, hash);
25
+ } catch (error: any) {
26
+ throw new Error(error); // TODO Improve error message for broadcast failure cases
27
+ }
28
+ };
29
+
30
+ export default broadcast;
@@ -0,0 +1,57 @@
1
+ import type { Account as WalletAccount } from "@ledgerhq/wallet-btc";
2
+ import { CoinSelect, DeepFirst, Merge } from "@ledgerhq/wallet-btc";
3
+ import type { TransactionInfo as WalletTxInfo } from "@ledgerhq/wallet-btc";
4
+ import { FeeNotLoaded } from "@ledgerhq/errors";
5
+
6
+ import type { Account } from "../../types";
7
+ import type { Transaction, UtxoStrategy } from "./types";
8
+ import { bitcoinPickingStrategy } from "./types";
9
+ import wallet, { getWalletAccount } from "./wallet";
10
+
11
+ const selectUtxoPickingStrategy = (
12
+ walletAccount: WalletAccount,
13
+ utxoStrategy: UtxoStrategy
14
+ ) => {
15
+ const handler = {
16
+ [bitcoinPickingStrategy.MERGE_OUTPUTS]: Merge,
17
+ [bitcoinPickingStrategy.DEEP_OUTPUTS_FIRST]: DeepFirst,
18
+ [bitcoinPickingStrategy.OPTIMIZE_SIZE]: CoinSelect,
19
+ }[utxoStrategy.strategy];
20
+ if (!handler) throw new Error("Unsupported Bitcoin UTXO picking strategy");
21
+
22
+ return new handler(
23
+ walletAccount.xpub.crypto,
24
+ walletAccount.xpub.derivationMode,
25
+ utxoStrategy.excludeUTXOs
26
+ );
27
+ };
28
+
29
+ export const buildTransaction = async (
30
+ account: Account,
31
+ transaction: Transaction
32
+ ): Promise<WalletTxInfo> => {
33
+ if (!transaction.feePerByte) {
34
+ throw new FeeNotLoaded();
35
+ }
36
+ const walletAccount = await getWalletAccount(account);
37
+ const utxoPickingStrategy = selectUtxoPickingStrategy(
38
+ walletAccount,
39
+ transaction.utxoStrategy
40
+ );
41
+
42
+ const maxSpendable = await wallet.estimateAccountMaxSpendable(
43
+ walletAccount,
44
+ transaction.feePerByte.toNumber() //!\ wallet-btc handles fees as JS number
45
+ );
46
+
47
+ const txInfo = await wallet.buildAccountTx({
48
+ fromAccount: walletAccount,
49
+ dest: transaction.recipient,
50
+ amount: transaction.useAllAmount ? maxSpendable : transaction.amount,
51
+ feePerByte: transaction.feePerByte.toNumber(), //!\ wallet-btc handles fees as JS number
52
+ utxoPickingStrategy,
53
+ // Definition of replaceable, per the standard: https://github.com/bitcoin/bips/blob/61ccc84930051e5b4a99926510d0db4a8475a4e6/bip-0125.mediawiki#summary
54
+ sequence: transaction.rbf ? 0 : 0xffffffff,
55
+ });
56
+ return txInfo;
57
+ };
@@ -0,0 +1,27 @@
1
+ import { BigNumber } from "bignumber.js";
2
+ import type { Transaction } from "./types";
3
+
4
+ /**
5
+ * Create an empty transaction
6
+ *
7
+ * @returns {Transaction}
8
+ */
9
+ const createTransaction = (): Transaction => {
10
+ return {
11
+ family: "bitcoin",
12
+ amount: new BigNumber(0),
13
+ utxoStrategy: {
14
+ strategy: 0,
15
+ pickUnconfirmedRBF: false,
16
+ excludeUTXOs: [],
17
+ },
18
+ recipient: "",
19
+ rbf: false,
20
+ feePerByte: null,
21
+ networkInfo: null,
22
+ useAllAmount: false,
23
+ feesStrategy: "medium",
24
+ };
25
+ };
26
+
27
+ export default createTransaction;
@@ -0,0 +1,36 @@
1
+ import { BigNumber } from "bignumber.js";
2
+ import type { AccountLike, Account } from "../../types";
3
+ import type { Transaction } from "./types";
4
+ import { getMainAccount } from "../../account";
5
+ import wallet, { getWalletAccount } from "./wallet";
6
+ import { getAccountNetworkInfo } from "./getAccountNetworkInfo";
7
+
8
+ /**
9
+ * Returns the maximum possible amount for transaction
10
+ *
11
+ * @param {Object} param - the account, parentAccount and transaction
12
+ */
13
+ const estimateMaxSpendable = async ({
14
+ account,
15
+ parentAccount,
16
+ transaction,
17
+ }: {
18
+ account: AccountLike;
19
+ parentAccount: Account | null | undefined;
20
+ transaction: Transaction | null | undefined;
21
+ }): Promise<BigNumber> => {
22
+ const mainAccount = getMainAccount(account, parentAccount);
23
+ const walletAccount = await getWalletAccount(mainAccount);
24
+ let feePerByte = transaction?.feePerByte;
25
+ if (!feePerByte) {
26
+ const networkInfo = await getAccountNetworkInfo(mainAccount);
27
+ feePerByte = networkInfo.feeItems.defaultFeePerByte;
28
+ }
29
+ const maxSpendable = await wallet.estimateAccountMaxSpendable(
30
+ walletAccount,
31
+ feePerByte.toNumber() //!\ wallet-btc handles fees as JS number
32
+ );
33
+ return maxSpendable.lt(0) ? new BigNumber(0) : maxSpendable;
34
+ };
35
+
36
+ export default estimateMaxSpendable;
@@ -0,0 +1,59 @@
1
+ import { BigNumber } from "bignumber.js";
2
+ import type { Account } from "./../../types";
3
+ import type { Transaction } from "./types";
4
+ import { buildTransaction } from "./js-buildTransaction";
5
+ import { perCoinLogic } from "./logic";
6
+
7
+ const getFeesForTransaction = async ({
8
+ account,
9
+ transaction,
10
+ }: {
11
+ account: Account;
12
+ transaction: Transaction;
13
+ }) => {
14
+ const walletTx = await buildTransaction(account, transaction);
15
+ const fees = new BigNumber(walletTx.fee).integerValue();
16
+ let txInputs = walletTx.inputs.map((i) => {
17
+ return {
18
+ address: i.address,
19
+ value: new BigNumber(i.value),
20
+ previousTxHash: i.output_hash,
21
+ previousOutputIndex: i.output_index,
22
+ };
23
+ });
24
+ let txOutputs = walletTx.outputs.map((o) => {
25
+ return {
26
+ outputIndex: walletTx.outputs.indexOf(o),
27
+ address: o.address,
28
+ isChange: o.isChange,
29
+ value: new BigNumber(o.value),
30
+ hash: undefined,
31
+ blockHeight: undefined,
32
+ rbf: transaction.rbf,
33
+ };
34
+ });
35
+ const perCoin = perCoinLogic[account.currency.id];
36
+
37
+ if (perCoin) {
38
+ const { syncReplaceAddress } = perCoin;
39
+
40
+ if (syncReplaceAddress) {
41
+ txInputs = txInputs.map((i) => ({
42
+ ...i,
43
+ address: i.address && syncReplaceAddress(i.address),
44
+ }));
45
+ txOutputs = txOutputs.map((o) => ({
46
+ ...o,
47
+ address: o.address && syncReplaceAddress(o.address),
48
+ }));
49
+ }
50
+ }
51
+
52
+ return {
53
+ fees,
54
+ txInputs,
55
+ txOutputs,
56
+ };
57
+ };
58
+
59
+ export default getFeesForTransaction;
@@ -0,0 +1,118 @@
1
+ import { BigNumber } from "bignumber.js";
2
+ import { log } from "@ledgerhq/logs";
3
+ import {
4
+ AmountRequired,
5
+ NotEnoughBalance,
6
+ FeeNotLoaded,
7
+ FeeTooHigh,
8
+ FeeRequired,
9
+ } from "@ledgerhq/errors";
10
+ import { LowerThanMinimumRelayFee } from "./../../errors";
11
+ import type { Account, TransactionStatus } from "./../../types";
12
+ import type { Transaction } from "./types";
13
+ import { calculateFees, validateRecipient } from "./cache";
14
+ import { getMinRelayFee } from "./logic";
15
+
16
+ const getTransactionStatus = async (
17
+ a: Account,
18
+ t: Transaction
19
+ ): Promise<TransactionStatus> => {
20
+ const errors: any = {};
21
+ const warnings: any = {};
22
+ const useAllAmount = !!t.useAllAmount;
23
+ const { recipientError, recipientWarning } = await validateRecipient(
24
+ a.currency,
25
+ t.recipient
26
+ );
27
+
28
+ if (recipientError) {
29
+ errors.recipient = recipientError;
30
+ }
31
+
32
+ if (recipientWarning) {
33
+ warnings.recipient = recipientWarning;
34
+ }
35
+
36
+ let txInputs;
37
+ let txOutputs;
38
+ let estimatedFees = new BigNumber(0);
39
+
40
+ if (!t.feePerByte) {
41
+ errors.feePerByte = new FeeNotLoaded();
42
+ } else if (t.feePerByte.eq(0)) {
43
+ errors.feePerByte = new FeeRequired();
44
+ } else if (t.recipient && !errors.recipient) {
45
+ await calculateFees({
46
+ account: a,
47
+ transaction: t,
48
+ }).then(
49
+ (res) => {
50
+ txInputs = res.txInputs;
51
+ txOutputs = res.txOutputs;
52
+ estimatedFees = res.fees;
53
+ },
54
+ (error) => {
55
+ if (error.name === "NotEnoughBalance") {
56
+ errors.amount = error;
57
+ } else {
58
+ throw error;
59
+ }
60
+ }
61
+ );
62
+ }
63
+
64
+ const sumOfInputs = (txInputs || []).reduce(
65
+ (sum, input) => sum.plus(input.value),
66
+ new BigNumber(0)
67
+ );
68
+ const sumOfChanges = (txOutputs || [])
69
+ .filter((o) => o.isChange)
70
+ .reduce((sum, output) => sum.plus(output.value), new BigNumber(0));
71
+
72
+ if (txInputs) {
73
+ log("bitcoin", `${txInputs.length} inputs, sum: ${sumOfInputs.toString()}`);
74
+ }
75
+
76
+ if (txOutputs) {
77
+ log(
78
+ "bitcoin",
79
+ `${txOutputs.length} outputs, sum of changes: ${sumOfChanges.toString()}`
80
+ );
81
+ }
82
+
83
+ const totalSpent = sumOfInputs.minus(sumOfChanges);
84
+ const amount = useAllAmount ? totalSpent.minus(estimatedFees) : t.amount;
85
+ log(
86
+ "bitcoin",
87
+ `totalSpent ${totalSpent.toString()} amount ${amount.toString()}`
88
+ );
89
+
90
+ if (!errors.amount && !amount.gt(0)) {
91
+ errors.amount = useAllAmount
92
+ ? new NotEnoughBalance()
93
+ : new AmountRequired();
94
+ }
95
+
96
+ if (
97
+ process.env.EXPERIMENTAL_MIN_RELAY_FEE &&
98
+ estimatedFees &&
99
+ estimatedFees.gt(0) &&
100
+ estimatedFees.lt(getMinRelayFee(a.currency))
101
+ ) {
102
+ warnings.feePerByte = new LowerThanMinimumRelayFee();
103
+ } else if (amount.gt(0) && estimatedFees.times(10).gt(amount)) {
104
+ warnings.feeTooHigh = new FeeTooHigh();
105
+ }
106
+
107
+ return Promise.resolve({
108
+ errors,
109
+ warnings,
110
+ estimatedFees,
111
+ amount,
112
+ totalSpent,
113
+ txInputs,
114
+ txOutputs,
115
+ });
116
+ };
117
+
118
+ export default getTransactionStatus;
@@ -0,0 +1,35 @@
1
+ import invariant from "invariant";
2
+ import type { Account } from "../../types";
3
+ import type { Transaction } from "./types";
4
+ import { getAccountNetworkInfo } from "./getAccountNetworkInfo";
5
+ import { inferFeePerByte } from "./logic";
6
+
7
+ const prepareTransaction = async (
8
+ a: Account,
9
+ t: Transaction
10
+ ): Promise<Transaction> => {
11
+ let networkInfo = t.networkInfo;
12
+
13
+ if (!networkInfo) {
14
+ networkInfo = await getAccountNetworkInfo(a);
15
+ invariant(networkInfo.family === "bitcoin", "bitcoin networkInfo expected");
16
+ }
17
+
18
+ const feePerByte = inferFeePerByte(t, networkInfo);
19
+
20
+ if (
21
+ t.networkInfo === networkInfo &&
22
+ (feePerByte === t.feePerByte || feePerByte.eq(t.feePerByte || 0))
23
+ ) {
24
+ // nothing changed
25
+ return t;
26
+ }
27
+
28
+ return {
29
+ ...t,
30
+ networkInfo,
31
+ feePerByte,
32
+ };
33
+ };
34
+
35
+ export default prepareTransaction;