@exodus/bitcoin-api 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bitcoin-api",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "Exodus bitcoin-api",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -13,7 +13,6 @@
13
13
  "access": "restricted"
14
14
  },
15
15
  "scripts": {
16
- "build": "babel --root-mode upward --delete-dir-on-start --ignore '**/__tests__/**' src --out-dir lib",
17
16
  "test": "jest",
18
17
  "lint": "eslint ./src",
19
18
  "lint:fix": "yarn lint --fix"
@@ -41,5 +40,5 @@
41
40
  "@exodus/bitcoin-meta": "^1.0.0",
42
41
  "@noble/secp256k1": "~1.5.3"
43
42
  },
44
- "gitHead": "17fcd32b82150b04903edc83c72b16a73ee00e86"
43
+ "gitHead": "e2dd97caae4a23757f38f2156450f17d86fdce34"
45
44
  }
@@ -0,0 +1,26 @@
1
+ import assert from 'minimalistic-assert'
2
+ import { getUtxos } from './utxos-utils'
3
+
4
+ // known issue!! fee data is static here!
5
+ // Hydra's balances's module does not provide it when calling asset.api.getBalances
6
+ // https://github.com/ExodusMovement/exodus-hydra/blob/f9110f8e9e76b8b199bc4d40461cb1bed3a5be1e/modules/balances/module/index.js#L130
7
+ // feeData is required to know if an unconfirmed tx can or cannot be RBFed?
8
+ // This could be fixed once we allow all unconfirmed utxos to be RBFed or if we change the balance module to provide the feeData when calling asset.api.getBalances
9
+ export const getBalancesFactory = ({ feeData, getSpendableBalance }) => {
10
+ assert(feeData, 'feeData is required')
11
+ assert(getSpendableBalance, 'getSpendableBalance is required')
12
+ return ({ asset, accountState, txLog }) => {
13
+ assert(asset, 'asset is required')
14
+ assert(accountState, 'accountState is required')
15
+ assert(txLog, 'txLog is required')
16
+ const utxos = getUtxos({ asset, accountState })
17
+ const balance = utxos.value
18
+ const spendableBalance = getSpendableBalance({
19
+ asset,
20
+ accountState,
21
+ txSet: txLog,
22
+ feeData,
23
+ })
24
+ return { balance, spendableBalance }
25
+ }
26
+ }
@@ -1,7 +1,7 @@
1
1
  import assert from 'minimalistic-assert'
2
2
  import { getUtxosData } from './utxo-selector'
3
3
  import { findUnconfirmedSentRbfTxs } from '../tx-utils'
4
- import { getSpendableUtxos, getUsableUtxos, getUtxos } from '../utxos-utils'
4
+ import { getUsableUtxos, getUtxos } from '../utxos-utils'
5
5
  import { canBumpTx } from './can-bump-tx'
6
6
 
7
7
  export class GetFeeResolver {
@@ -40,15 +40,13 @@ export class GetFeeResolver {
40
40
  }
41
41
 
42
42
  getSpendableBalance = ({ asset, accountState, txSet, feeData }) => {
43
- const utxos = getUtxos({ accountState, asset })
44
- const spendableUtxos = getSpendableUtxos({
43
+ return this.#getUtxosData({
45
44
  asset,
46
- utxos,
47
- feeData,
45
+ accountState,
48
46
  txSet,
49
- allowUnconfirmedRbfEnabledUtxos: this.#allowUnconfirmedRbfEnabledUtxos,
50
- })
51
- return spendableUtxos.value
47
+ feeData,
48
+ isSendAll: true,
49
+ }).spendableBalance
52
50
  }
53
51
 
54
52
  #getUtxosData = ({ asset, accountState, txSet, feeData, amount, customFee, isSendAll }) => {
@@ -209,12 +209,15 @@ export const getUtxosData = ({
209
209
  })
210
210
 
211
211
  const resolvedFee = replaceTx ? fee.sub(replaceTx.feeAmount) : fee
212
-
213
- const spendableBalance = getConfirmedOrRfbDisabledUtxos({
212
+ const empty = UtxoCollection.createEmpty({ currency: asset.currency })
213
+ const spendableUtxos = getConfirmedOrRfbDisabledUtxos({
214
214
  asset,
215
215
  utxos: usableUtxos,
216
216
  allowUnconfirmedRbfEnabledUtxos,
217
- }).value
217
+ }).union(replaceTx ? usableUtxos.getTxIdUtxos(replaceTx.txId) : empty)
218
+ // .union(selectedUtxos || empty)
219
+
220
+ const spendableBalance = spendableUtxos.value
218
221
 
219
222
  const extraFee = selectedUtxos
220
223
  ? asset.currency.baseUnit(getExtraFee({ asset, inputs: selectedUtxos, feePerKB: feeRate }))
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './account-state'
2
+ export * from './balances'
2
3
  export * from './btc-address'
3
4
  export * from './btc-like-address'
4
5
  export * from './btc-like-keys'
package/src/move-funds.js CHANGED
@@ -2,21 +2,45 @@ import wif from 'wif'
2
2
  import { UtxoCollection, Address } from '@exodus/models'
3
3
  import { createInputs, createOutput, getNonWitnessTxs } from './tx-send'
4
4
  import assert from 'minimalistic-assert'
5
+ import secp256k1 from 'secp256k1'
6
+
7
+ function wifToPublicKey({ coinInfo, privateKeyWIF }) {
8
+ assert(coinInfo, 'coinInfo is required')
9
+ assert(privateKeyWIF, 'privateKeyWIF is required')
10
+ const { versions } = coinInfo
11
+
12
+ const { privateKey, compressed } = wif.decode(privateKeyWIF, versions.private)
13
+ return secp256k1.publicKeyCreate(privateKey, compressed)
14
+ }
15
+
16
+ export const getAddressesFromPrivateKeyFactory = ({ purposes, keys, coinInfo }) => {
17
+ assert(purposes, 'purposes is required')
18
+ assert(keys, 'keys is required')
19
+ assert(coinInfo, 'coinInfo is required')
20
+ return ({ privateKey }) => {
21
+ const publicKey = wifToPublicKey({ coinInfo, privateKeyWIF: privateKey })
22
+ return purposes.map((purpose) =>
23
+ keys.encodePublic(purpose === 49 ? secp256k1.publicKeyConvert(publicKey, true) : publicKey, {
24
+ purpose,
25
+ })
26
+ )
27
+ }
28
+ }
5
29
 
6
30
  export const moveFundsFactory = ({
7
31
  asset,
8
32
  insightClient,
9
33
  getFeeEstimator,
10
- keys,
11
34
  signTx,
12
35
  address,
36
+ getAddressesFromPrivateKey,
13
37
  }) => {
14
38
  assert(asset, 'asset is required')
15
39
  assert(insightClient, 'insightClient is required')
16
40
  assert(getFeeEstimator, 'getFeeEstimator is required')
17
41
  assert(address, 'address is required')
18
- assert(keys, 'keys is required')
19
42
  assert(signTx, 'signTx is required')
43
+ assert(getAddressesFromPrivateKey, 'getAddressesFromPrivateKey is required')
20
44
  async function prepareFunds(assetName, input, options = {}) {
21
45
  const { toAddress, assetClientInterface, MoveFundsError, walletAccount } = options
22
46
  assert(MoveFundsError, 'MoveFundsError is required') // should we move MoveFundsError to asset libs?
@@ -29,6 +53,7 @@ export const moveFundsFactory = ({
29
53
  asset,
30
54
  input,
31
55
  }
56
+ // WIF format private key
32
57
  const privateKey = input
33
58
 
34
59
  if (!isValidPrivateKey(privateKey)) {
@@ -36,7 +61,7 @@ export const moveFundsFactory = ({
36
61
  }
37
62
 
38
63
  const { compressed } = wif.decode(privateKey)
39
- const addresses = getAllAddressesFromWIF(privateKey)
64
+ const addresses = getAddressesFromPrivateKey({ privateKey })
40
65
 
41
66
  const receiveAddresses = await assetClientInterface.getReceiveAddresses({
42
67
  walletAccount,
@@ -61,7 +86,9 @@ export const moveFundsFactory = ({
61
86
  }
62
87
  }
63
88
  if (!found) {
64
- formatProps.fromAddress = addresses.join(' or ')
89
+ formatProps.fromAddress = `${addresses.slice(0, -1).join(', ')}, or ${
90
+ addresses[addresses.length - 1]
91
+ }`
65
92
  throw new MoveFundsError('balance-zero', formatProps)
66
93
  }
67
94
  const fromAddress = address
@@ -114,35 +141,6 @@ export const moveFundsFactory = ({
114
141
  return { txId, fromAddress, toAddress, amount, fee }
115
142
  }
116
143
 
117
- function getAllAddressesFromWIF(privateKeyWIF) {
118
- const { compressed } = wif.decode(privateKeyWIF)
119
- const legacyAddress = keys.encodePublicFromWIF(privateKeyWIF)
120
- // TODO: support nested segwit address, right now we don't support send
121
- // const nestedSegwitAddress = encodeNestedSegwitFromWIF(privateKeyWIF, { asset })
122
- const nativeSegwitAddress = keys.encodePublicBech32FromWIF(privateKeyWIF)
123
-
124
- if (compressed) {
125
- return [nativeSegwitAddress, legacyAddress]
126
- } else {
127
- return [legacyAddress]
128
- }
129
- }
130
-
131
- /*
132
- function getPublicFromWIF(privateKeyWIF, { asset }) {
133
- const { versions } = asset.coinInfo
134
- const { privateKey, compressed } = wif.decode(privateKeyWIF, versions.private)
135
- return secp256k1.pointFromScalar(privateKey, compressed)
136
- }
137
-
138
- function encodeNestedSegwitFromWIF(privateKeyWIF, { asset }) {
139
- const publicKey = getPublicFromWIF(privateKeyWIF, { asset })
140
- const witnessProgram = bitcoinjs.payments.p2wpkh({ pubkey: publicKey }).output
141
- const witnessProgramHash = bitcoinjs.crypto.hash160(witnessProgram)
142
- return bitcoinjs.address.toBase58Check(witnessProgramHash, asset.coinInfo.versions.scripthash)
143
- }
144
- */
145
-
146
144
  async function getUtxos({ asset, address }) {
147
145
  const rawUtxos = await insightClient.fetchUTXOs([address])
148
146
  return UtxoCollection.fromArray(
@@ -363,7 +363,7 @@ export const createAndBroadcastTXFactory = ({
363
363
  sent: selfSend ? [] : receivers,
364
364
  rbfEnabled,
365
365
  feePerKB: ['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(assetName)
366
- ? fee.div(tx.virtualSize() / 1000).toBaseNumber()
366
+ ? fee.div(tx.virtualSize / 1000).toBaseNumber()
367
367
  : undefined,
368
368
  changeAddress: changeOutput ? ourAddress : undefined,
369
369
  blockHeight,
@@ -115,6 +115,7 @@ export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, netwo
115
115
  const rawTx = tx.toBuffer()
116
116
  const txId = tx.getId()
117
117
 
118
- return { rawTx, txId, tx }
118
+ // tx needs to be serializable for desktop RPC send => sign communication
119
+ return { rawTx, txId, tx: { ...tx, virtualSize: tx.virtualSize?.() } }
119
120
  }
120
121
  }
@@ -12,26 +12,6 @@ export function getUtxos({ accountState, asset }) {
12
12
  )
13
13
  }
14
14
 
15
- export const getBalancesFactory = ({ feeData, allowUnconfirmedRbfEnabledUtxos }) => {
16
- assert(feeData, 'feeData is required')
17
- return ({ asset, accountState, txLog }) => {
18
- assert(asset, 'asset is required')
19
- assert(accountState, 'accountState is required')
20
- assert(txLog, 'txLog is required')
21
- const utxos = getUtxos({ asset, accountState })
22
- const balance = utxos.value
23
- const spendableBalance = getSpendableUtxos({
24
- asset,
25
- utxos,
26
- txSet: txLog,
27
- feeData,
28
-
29
- allowUnconfirmedRbfEnabledUtxos,
30
- }).value
31
- return { balance, spendableBalance }
32
- }
33
- }
34
-
35
15
  export function getConfirmedUtxos({ asset, utxos }) {
36
16
  assert(asset, 'asset is required')
37
17
  assert(utxos, 'utxos is required')
@@ -83,19 +63,3 @@ export function getUsableUtxos({ asset, utxos, feeData, txSet }) {
83
63
  { currency: asset.currency }
84
64
  )
85
65
  }
86
-
87
- export function getSpendableUtxos({
88
- asset,
89
- utxos,
90
- feeData,
91
- txSet,
92
-
93
- allowUnconfirmedRbfEnabledUtxos,
94
- }) {
95
- const usableUtxos = getUsableUtxos({ asset, utxos, feeData, txSet })
96
- return getConfirmedOrRfbDisabledUtxos({
97
- asset,
98
- utxos: usableUtxos,
99
- allowUnconfirmedRbfEnabledUtxos,
100
- })
101
- }