@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.
- package/lib/__tests__/families/bitcoin/logic.d.ts +2 -0
- package/lib/__tests__/families/bitcoin/logic.d.ts.map +1 -0
- package/lib/__tests__/families/bitcoin/logic.js +39 -0
- package/lib/__tests__/families/bitcoin/logic.js.map +1 -0
- package/lib/__tests__/test-helpers/mockDevice.d.ts.map +1 -1
- package/lib/__tests__/test-helpers/mockDevice.js +0 -1
- package/lib/__tests__/test-helpers/mockDevice.js.map +1 -1
- package/lib/account/addAccounts.d.ts.map +1 -1
- package/lib/account/addAccounts.js +2 -1
- package/lib/account/addAccounts.js.map +1 -1
- package/lib/account/support.d.ts.map +1 -1
- package/lib/account/support.js +24 -1
- package/lib/account/support.js.map +1 -1
- package/lib/api/explorerConfig/index.js +15 -15
- package/lib/bridge/jsHelpers.d.ts +6 -1
- package/lib/bridge/jsHelpers.d.ts.map +1 -1
- package/lib/bridge/jsHelpers.js +11 -3
- package/lib/bridge/jsHelpers.js.map +1 -1
- package/lib/families/bitcoin/account.d.ts.map +1 -1
- package/lib/families/bitcoin/account.js +3 -2
- package/lib/families/bitcoin/account.js.map +1 -1
- package/lib/families/bitcoin/bridge/js.d.ts +8 -0
- package/lib/families/bitcoin/bridge/js.d.ts.map +1 -0
- package/lib/families/bitcoin/bridge/js.js +149 -0
- package/lib/families/bitcoin/bridge/js.js.map +1 -0
- package/lib/families/bitcoin/cache.d.ts +28 -0
- package/lib/families/bitcoin/cache.d.ts.map +1 -0
- package/lib/families/bitcoin/cache.js +109 -0
- package/lib/families/bitcoin/cache.js.map +1 -0
- package/lib/families/bitcoin/datasets/bitcoin.js +6 -6
- package/lib/families/bitcoin/datasets/bitcoin.js.map +1 -1
- package/lib/families/bitcoin/datasets/litecoin.js +3 -3
- package/lib/families/bitcoin/datasets/litecoin.js.map +1 -1
- package/lib/families/bitcoin/getAccountNetworkInfo.d.ts +6 -0
- package/lib/families/bitcoin/getAccountNetworkInfo.d.ts.map +1 -0
- package/lib/families/bitcoin/getAccountNetworkInfo.js +124 -0
- package/lib/families/bitcoin/getAccountNetworkInfo.js.map +1 -0
- package/lib/families/bitcoin/getAccountNetworkInfo.test.d.ts +2 -0
- package/lib/families/bitcoin/getAccountNetworkInfo.test.d.ts.map +1 -0
- package/lib/families/bitcoin/getAccountNetworkInfo.test.js +71 -0
- package/lib/families/bitcoin/getAccountNetworkInfo.test.js.map +1 -0
- package/lib/families/bitcoin/js-broadcast.d.ts +11 -0
- package/lib/families/bitcoin/js-broadcast.d.ts.map +1 -0
- package/lib/families/bitcoin/js-broadcast.js +80 -0
- package/lib/families/bitcoin/js-broadcast.js.map +1 -0
- package/lib/families/bitcoin/js-buildTransaction.d.ts +5 -0
- package/lib/families/bitcoin/js-buildTransaction.d.ts.map +1 -0
- package/lib/families/bitcoin/js-buildTransaction.js +106 -0
- package/lib/families/bitcoin/js-buildTransaction.js.map +1 -0
- package/lib/families/bitcoin/js-createTransaction.d.ts +9 -0
- package/lib/families/bitcoin/js-createTransaction.d.ts.map +1 -0
- package/lib/families/bitcoin/js-createTransaction.js +27 -0
- package/lib/families/bitcoin/js-createTransaction.js.map +1 -0
- package/lib/families/bitcoin/js-estimateMaxSpendable.d.ts +15 -0
- package/lib/families/bitcoin/js-estimateMaxSpendable.d.ts.map +1 -0
- package/lib/families/bitcoin/js-estimateMaxSpendable.js +95 -0
- package/lib/families/bitcoin/js-estimateMaxSpendable.js.map +1 -0
- package/lib/families/bitcoin/js-getFeesForTransaction.d.ts +26 -0
- package/lib/families/bitcoin/js-getFeesForTransaction.d.ts.map +1 -0
- package/lib/families/bitcoin/js-getFeesForTransaction.js +100 -0
- package/lib/families/bitcoin/js-getFeesForTransaction.js.map +1 -0
- package/lib/families/bitcoin/js-getTransactionStatus.d.ts +5 -0
- package/lib/families/bitcoin/js-getTransactionStatus.d.ts.map +1 -0
- package/lib/families/bitcoin/js-getTransactionStatus.js +131 -0
- package/lib/families/bitcoin/js-getTransactionStatus.js.map +1 -0
- package/lib/families/bitcoin/js-prepareTransaction.d.ts +5 -0
- package/lib/families/bitcoin/js-prepareTransaction.d.ts.map +1 -0
- package/lib/families/bitcoin/js-prepareTransaction.js +80 -0
- package/lib/families/bitcoin/js-prepareTransaction.js.map +1 -0
- package/lib/families/bitcoin/js-signOperation.d.ts +10 -0
- package/lib/families/bitcoin/js-signOperation.d.ts.map +1 -0
- package/lib/families/bitcoin/js-signOperation.js +212 -0
- package/lib/families/bitcoin/js-signOperation.js.map +1 -0
- package/lib/families/bitcoin/js-synchronisation.d.ts +10 -0
- package/lib/families/bitcoin/js-synchronisation.d.ts.map +1 -0
- package/lib/families/bitcoin/js-synchronisation.js +411 -0
- package/lib/families/bitcoin/js-synchronisation.js.map +1 -0
- package/lib/families/bitcoin/logic.d.ts +34 -0
- package/lib/families/bitcoin/logic.d.ts.map +1 -0
- package/lib/families/bitcoin/logic.js +290 -0
- package/lib/families/bitcoin/logic.js.map +1 -0
- package/lib/families/bitcoin/networks.d.ts +3 -0
- package/lib/families/bitcoin/networks.d.ts.map +1 -0
- package/lib/families/bitcoin/networks.js +379 -0
- package/lib/families/bitcoin/networks.js.map +1 -0
- package/lib/families/bitcoin/serialization.d.ts +2 -2
- package/lib/families/bitcoin/serialization.d.ts.map +1 -1
- package/lib/families/bitcoin/serialization.js +8 -4
- package/lib/families/bitcoin/serialization.js.map +1 -1
- package/lib/families/bitcoin/specs.d.ts.map +1 -1
- package/lib/families/bitcoin/specs.js +8 -5
- package/lib/families/bitcoin/specs.js.map +1 -1
- package/lib/families/bitcoin/speculos-deviceActions.js +2 -2
- package/lib/families/bitcoin/speculos-deviceActions.js.map +1 -1
- package/lib/families/bitcoin/test-dataset.js +1 -1
- package/lib/families/bitcoin/test-dataset.js.map +1 -1
- package/lib/families/bitcoin/transaction.d.ts +3 -3
- package/lib/families/bitcoin/transaction.d.ts.map +1 -1
- package/lib/families/bitcoin/transaction.js +64 -61
- package/lib/families/bitcoin/transaction.js.map +1 -1
- package/lib/families/bitcoin/types.d.ts +66 -36
- package/lib/families/bitcoin/types.d.ts.map +1 -1
- package/lib/families/bitcoin/types.js +16 -1
- package/lib/families/bitcoin/types.js.map +1 -1
- package/lib/families/bitcoin/wallet.d.ts +6 -0
- package/lib/families/bitcoin/wallet.d.ts.map +1 -0
- package/lib/families/bitcoin/wallet.js +66 -0
- package/lib/families/bitcoin/wallet.js.map +1 -0
- package/lib/generated/bridge/js.d.ts +4 -0
- package/lib/generated/bridge/js.d.ts.map +1 -1
- package/lib/generated/bridge/js.js +20 -18
- package/lib/generated/bridge/js.js.map +1 -1
- package/lib/migrations/accounts-dataset.d.ts.map +1 -1
- package/lib/migrations/accounts-dataset.js +4 -2
- package/lib/migrations/accounts-dataset.js.map +1 -1
- package/lib/migrations/accounts.d.ts.map +1 -1
- package/lib/migrations/accounts.js +17 -0
- package/lib/migrations/accounts.js.map +1 -1
- package/lib/mock/account.d.ts.map +1 -1
- package/lib/mock/account.js +2 -1
- package/lib/mock/account.js.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/__snapshots__/all.libcore.ts.snap +0 -125
- package/src/__tests__/families/bitcoin/logic.ts +40 -0
- package/src/__tests__/test-helpers/mockDevice.ts +0 -1
- package/src/account/addAccounts.ts +2 -1
- package/src/account/support.ts +24 -1
- package/src/api/explorerConfig/index.ts +15 -15
- package/src/bridge/jsHelpers.ts +22 -2
- package/src/families/bitcoin/account.ts +2 -1
- package/src/families/bitcoin/bridge/js.ts +76 -0
- package/src/families/bitcoin/cache.ts +76 -0
- package/src/families/bitcoin/datasets/bitcoin.ts +6 -6
- package/src/families/bitcoin/datasets/litecoin.ts +6 -6
- package/src/families/bitcoin/getAccountNetworkInfo.test.ts +27 -0
- package/src/families/bitcoin/getAccountNetworkInfo.ts +61 -0
- package/src/families/bitcoin/js-broadcast.ts +30 -0
- package/src/families/bitcoin/js-buildTransaction.ts +57 -0
- package/src/families/bitcoin/js-createTransaction.ts +27 -0
- package/src/families/bitcoin/js-estimateMaxSpendable.ts +36 -0
- package/src/families/bitcoin/js-getFeesForTransaction.ts +59 -0
- package/src/families/bitcoin/js-getTransactionStatus.ts +118 -0
- package/src/families/bitcoin/js-prepareTransaction.ts +35 -0
- package/src/families/bitcoin/js-signOperation.ts +164 -0
- package/src/families/bitcoin/js-synchronisation.ts +362 -0
- package/src/families/bitcoin/logic.ts +255 -0
- package/src/families/bitcoin/networks.ts +360 -0
- package/src/families/bitcoin/serialization.ts +6 -0
- package/src/families/bitcoin/specs.ts +5 -2
- package/src/families/bitcoin/speculos-deviceActions.ts +1 -1
- package/src/families/bitcoin/test-dataset.ts +1 -1
- package/src/families/bitcoin/transaction.ts +98 -93
- package/src/families/bitcoin/types.ts +95 -37
- package/src/families/bitcoin/wallet.ts +21 -0
- package/src/families/ethereum/modules/__snapshots__/compound.resilience.test.ts.snap +50 -9
- package/src/generated/bridge/js.ts +3 -0
- package/src/migrations/accounts-dataset.ts +3 -0
- package/src/migrations/accounts.ts +18 -0
- 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;
|