edge-currency-plugins 3.13.0 → 3.15.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 (29) hide show
  1. package/android/src/main/assets/edge-currency-plugins/edge-currency-plugins.js +47 -47
  2. package/lib/common/constants.js +4 -1
  3. package/lib/common/fees/calcMinerFeePerByte.js +19 -5
  4. package/lib/common/fees/calcReplacementFeeRate.js +25 -0
  5. package/lib/common/fees/processMempoolSpaceFees.js +12 -16
  6. package/lib/common/utxobased/engine/UtxoEngine.js +16 -3
  7. package/lib/common/utxobased/engine/types.js +3 -3
  8. package/lib/common/utxobased/info/bitcoin.js +1 -1
  9. package/lib/common/utxobased/info/bitcointestnet.js +1 -1
  10. package/lib/common/utxobased/info/bitcointestnet4.js +1 -1
  11. package/lib/common/utxobased/info/ecash.js +4 -1
  12. package/lib/common/utxobased/info/pivx.js +4 -4
  13. package/lib/common/utxobased/keymanager/keymanager.js +78 -0
  14. package/lib/common/utxobased/keymanager/types.js +5 -1
  15. package/lib/common/utxobased/keymanager/utxopicker/accumulative.js +4 -4
  16. package/lib/common/utxobased/keymanager/utxopicker/forceUseUtxo.js +5 -5
  17. package/lib/common/utxobased/keymanager/utxopicker/subtractFee.js +1 -1
  18. package/lib/common/utxobased/keymanager/utxopicker/utils.js +27 -3
  19. package/lib/src/common/constants.d.ts +1 -1
  20. package/lib/src/common/fees/calcReplacementFeeRate.d.ts +15 -0
  21. package/lib/src/common/utxobased/engine/types.d.ts +1 -1
  22. package/lib/src/common/utxobased/keymanager/keymanager.d.ts +6 -0
  23. package/lib/src/common/utxobased/keymanager/types.d.ts +5 -1
  24. package/lib/src/common/utxobased/keymanager/utxopicker/utils.d.ts +12 -0
  25. package/lib/test/common/fees/calcReplacementFeeRate.spec.d.ts +1 -0
  26. package/lib/test/common/fees/processMempoolSpaceFees.spec.d.ts +1 -0
  27. package/lib/test/common/utxobased/keymanager/utxopicker/decimalFeeRate.spec.d.ts +1 -0
  28. package/lib/test/common/utxobased/keymanager/utxopicker/utils.spec.d.ts +1 -0
  29. package/package.json +1 -1
@@ -3,7 +3,10 @@
3
3
  // Fees
4
4
  const FEES_PATH = 'fees.json'; exports.FEES_PATH = FEES_PATH
5
5
  const MAX_FEE = 999999999.0; exports.MAX_FEE = MAX_FEE
6
- const LOW_FEE = 2; exports.LOW_FEE = LOW_FEE
6
+ // Fee rates are sat/vB and may be fractional. Three decimal places matches the
7
+ // precision of the mempool.space `/fees/precise` endpoint, which is the most
8
+ // precise rate source we consume.
9
+ const FEE_RATE_DECIMALS = 3; exports.FEE_RATE_DECIMALS = FEE_RATE_DECIMALS
7
10
  const MAX_HIGH_DELAY = 200; exports.MAX_HIGH_DELAY = MAX_HIGH_DELAY
8
11
  const MAX_STANDARD_DELAY = 6; exports.MAX_STANDARD_DELAY = MAX_STANDARD_DELAY
9
12
  const MIN_STANDARD_DELAY = 2; exports.MIN_STANDARD_DELAY = MIN_STANDARD_DELAY
@@ -1,6 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _biggystring = require('biggystring');
2
2
 
3
3
 
4
+ var _constants = require('../constants');
4
5
 
5
6
 
6
7
 
@@ -29,10 +30,20 @@
29
30
  } = feeInfo
30
31
  let { highFee, lowFee, standardFeeHigh, standardFeeLow } = feeInfo
31
32
 
32
- highFee = _biggystring.round.call(void 0, _biggystring.mul.call(void 0, highFee, highFeeFudgeFactor), 0)
33
- lowFee = _biggystring.round.call(void 0, _biggystring.mul.call(void 0, lowFee, lowFeeFudgeFactor), 0)
34
- standardFeeHigh = _biggystring.round.call(void 0, _biggystring.mul.call(void 0, standardFeeHigh, standardFeeHighFudgeFactor), 0)
35
- standardFeeLow = _biggystring.round.call(void 0, _biggystring.mul.call(void 0, standardFeeLow, standardFeeLowFudgeFactor), 0)
33
+ // Fee rates may be fractional, so these round to FEE_RATE_DECIMALS places
34
+ // rather than to a whole sat/vB. biggystring's `round` takes the power of ten
35
+ // to round at, so the precision is negated: -3 means three decimal places.
36
+ const feeRatePlaces = -_constants.FEE_RATE_DECIMALS
37
+ highFee = _biggystring.round.call(void 0, _biggystring.mul.call(void 0, highFee, highFeeFudgeFactor), feeRatePlaces)
38
+ lowFee = _biggystring.round.call(void 0, _biggystring.mul.call(void 0, lowFee, lowFeeFudgeFactor), feeRatePlaces)
39
+ standardFeeHigh = _biggystring.round.call(void 0,
40
+ _biggystring.mul.call(void 0, standardFeeHigh, standardFeeHighFudgeFactor),
41
+ feeRatePlaces
42
+ )
43
+ standardFeeLow = _biggystring.round.call(void 0,
44
+ _biggystring.mul.call(void 0, standardFeeLow, standardFeeLowFudgeFactor),
45
+ feeRatePlaces
46
+ )
36
47
 
37
48
  switch (networkFeeOption) {
38
49
  case 'low':
@@ -55,7 +66,10 @@
55
66
 
56
67
  // Add this much to the low fee = (amountDiffFromLow * lowHighFeeDiff) / lowHighAmountDiff)
57
68
  const temp1 = _biggystring.mul.call(void 0, amountDiffFromLow, lowHighFeeDiff)
58
- const addFeeToLow = _biggystring.div.call(void 0, temp1, lowHighAmountDiff)
69
+ // `div` defaults to zero decimal places, which would truncate the
70
+ // interpolated rate back to a whole sat/vB. Unlike `round` above, its
71
+ // precision argument is the number of decimal places, not a power of ten.
72
+ const addFeeToLow = _biggystring.div.call(void 0, temp1, lowHighAmountDiff, _constants.FEE_RATE_DECIMALS)
59
73
  return _biggystring.add.call(void 0, standardFeeLow, addFeeToLow)
60
74
  }
61
75
 
@@ -0,0 +1,25 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _constants = require('../constants');
2
+
3
+ /**
4
+ * Calculate the sat/vB fee rate for a transaction that replaces another by fee:
5
+ * double the rate the replaced transaction paid.
6
+ *
7
+ * The rate rounds up to FEE_RATE_DECIMALS places. Rounding up means the
8
+ * replacement never pays less than double, and a positive rate never rounds
9
+ * down to zero, which the UTXO picker would accept as a valid rate. Rounding
10
+ * to a fixed number of places keeps the rate readable where it is reported as
11
+ * `feeRateUsed`.
12
+ *
13
+ * @param replacedFee The fee the replaced transaction paid, in satoshis
14
+ * @param replacedVBytes The replaced transaction's virtual size
15
+ * @returns {number}
16
+ */
17
+ const calcReplacementFeeRate = (
18
+ replacedFee,
19
+ replacedVBytes
20
+ ) => {
21
+ const scale = 10 ** _constants.FEE_RATE_DECIMALS
22
+ // The fee and size are whole numbers, so this division is exact whenever the
23
+ // scaled rate is, and Math.ceil sees no floating point noise.
24
+ return Math.ceil((replacedFee * 2 * scale) / replacedVBytes) / scale
25
+ }; exports.calcReplacementFeeRate = calcReplacementFeeRate
@@ -1,7 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } }var _biggystring = require('biggystring'); var bs = _interopRequireWildcard(_biggystring);
2
2
  var _cleaners = require('cleaners');
3
3
 
4
- var _constants = require('../constants');
5
4
 
6
5
 
7
6
  const asMempoolSpaceFees = _cleaners.asObject.call(void 0, {
@@ -12,13 +11,16 @@ var _constants = require('../constants');
12
11
 
13
12
  /**
14
13
  * Calculates the FeeRates object from MempoolSpace
15
- * Sets the minimum of LOW_FEE on all fee levels
16
14
  * @param fees
17
15
  * @returns Partial<FeeRates>
18
16
  */
19
17
 
20
- const STANDARD_FEE_HIGH_MULTIPLIER = 1.3
21
- const HIGH_FEE_MULTIPLIER = 2
18
+ // The `/fees/precise` endpoint reports rates with up to three decimal places,
19
+ // so the multipliers run through biggystring rather than JS numbers. A float
20
+ // multiply would reintroduce noise (0.575 * 1.3 is 0.7474999999999999) that
21
+ // biggystring carries exactly.
22
+ const STANDARD_FEE_HIGH_MULTIPLIER = '1.3'
23
+ const HIGH_FEE_MULTIPLIER = '2'
22
24
 
23
25
  const processMempoolSpaceFees = (fees) => {
24
26
  let mempoolFees
@@ -28,19 +30,13 @@ const HIGH_FEE_MULTIPLIER = 2
28
30
  return null
29
31
  }
30
32
 
31
- const { fastestFee, halfHourFee } = mempoolFees
32
-
33
- const lowFee = halfHourFee.toString()
34
- const standardFeeLow = fastestFee.toString()
35
- const standardFeeHigh = Math.round(
36
- fastestFee * STANDARD_FEE_HIGH_MULTIPLIER
37
- ).toString()
38
- const highFee = Math.round(fastestFee * HIGH_FEE_MULTIPLIER).toString()
33
+ const fastestFee = mempoolFees.fastestFee.toString()
34
+ const halfHourFee = mempoolFees.halfHourFee.toString()
39
35
 
40
36
  return {
41
- lowFee: bs.max(lowFee, _constants.LOW_FEE.toString()),
42
- standardFeeLow: bs.max(standardFeeLow, _constants.LOW_FEE.toString()),
43
- standardFeeHigh: bs.max(standardFeeHigh, _constants.LOW_FEE.toString()),
44
- highFee: bs.max(highFee, _constants.LOW_FEE.toString())
37
+ lowFee: halfHourFee,
38
+ standardFeeLow: fastestFee,
39
+ standardFeeHigh: bs.mul(fastestFee, STANDARD_FEE_HIGH_MULTIPLIER),
40
+ highFee: bs.mul(fastestFee, HIGH_FEE_MULTIPLIER)
45
41
  }
46
42
  }; exports.processMempoolSpaceFees = processMempoolSpaceFees
@@ -20,6 +20,7 @@ var _edgecorejs = require('edge-core-js');
20
20
 
21
21
  var _filterUndefined = require('../../../util/filterUndefined');
22
22
  var _unixTime = require('../../../util/unixTime');
23
+ var _calcReplacementFeeRate = require('../../fees/calcReplacementFeeRate');
23
24
  var _makeFees = require('../../fees/makeFees');
24
25
  var _EngineEmitter = require('../../plugin/EngineEmitter');
25
26
  var _Metadata = require('../../plugin/Metadata');
@@ -187,9 +188,12 @@ var _UtxoWalletTools = require('./UtxoWalletTools');
187
188
  // Must not be confirmed or dropped.
188
189
  if (replacedTx.blockHeight !== 0) return null
189
190
 
190
- // Double the fee used for the RBF transaction:
191
+ // Double the fee rate used by the replaced transaction:
191
192
  const vBytes = _utils.transactionSizeFromHex.call(void 0, replacedTx.hex)
192
- const newFeeRate = Math.round((parseInt(replacedTx.fees) / vBytes) * 2)
193
+ const newFeeRate = _calcReplacementFeeRate.calcReplacementFeeRate.call(void 0,
194
+ parseInt(replacedTx.fees),
195
+ vBytes
196
+ )
193
197
 
194
198
  const replacedTxInputs = replacedTx.inputs
195
199
  // Recreate UTXOs from DataLayer transaction and mark them as unspent:
@@ -631,7 +635,16 @@ var _UtxoWalletTools = require('./UtxoWalletTools');
631
635
  freshAddress.segwitAddress)), () => (
632
636
  freshAddress.publicAddress))
633
637
 
634
- const feeRate = parseInt(await fees.getRate(edgeSpendInfo))
638
+ // Fee rates may be fractional (e.g. a 1.8 sat/vB custom fee), so this
639
+ // must not be truncated to an integer. The picker rounds the satoshi
640
+ // amounts it derives from this rate up to whole satoshis. Number rejects
641
+ // any string that is not entirely a number, where parseFloat would read a
642
+ // comma-decimal '1,8' as 1 and charge the wrong rate without an error.
643
+ const feeRateString = await fees.getRate(edgeSpendInfo)
644
+ const feeRate = Number(feeRateString)
645
+ if (!isFinite(feeRate) || feeRate <= 0) {
646
+ throw new Error(`Invalid fee rate: ${feeRateString}`)
647
+ }
635
648
 
636
649
  let maxUtxo
637
650
  if (txOptions.CPFP != null) {
@@ -45,9 +45,9 @@ var _types5 = require('../keymanager/types');
45
45
  const asUtxoSignMessageOtherParams = _cleaners.asObject.call(void 0, {
46
46
  publicAddress: _cleaners.asString,
47
47
  // Defaults to the legacy Electrum encoding so existing callers keep the
48
- // format they already produce. BIP-137 is opt-in, since emitting a BIP-137
49
- // header to a verifier expecting the legacy one is just as broken as the
50
- // reverse.
48
+ // format they already produce. BIP-137 and BIP-322 are opt-in, since
49
+ // emitting either to a verifier expecting the legacy encoding is just as
50
+ // broken as the reverse.
51
51
  signatureFormat: _cleaners.asMaybe.call(void 0, _types5.asUtxoSignatureFormat, 'electrum')
52
52
  }); exports.asUtxoSignMessageOtherParams = asUtxoSignMessageOtherParams
53
53
 
@@ -65,7 +65,7 @@ const engineInfo = {
65
65
  forks: ['bitcoincash', 'bitcoingold'],
66
66
  gapLimit: 25,
67
67
  feeUpdateInterval: 60000,
68
- mempoolSpaceFeeInfoServer: 'https://mempool.space/api/v1/fees/recommended',
68
+ mempoolSpaceFeeInfoServer: 'https://mempool.space/api/v1/fees/precise',
69
69
  defaultFeeInfo: {
70
70
  lowFeeFudgeFactor: undefined,
71
71
  standardFeeLowFudgeFactor: undefined,
@@ -45,7 +45,7 @@ var _commonInfo = require('./commonInfo');
45
45
  forks: ['bitcoincash', 'bitcoingold'],
46
46
  gapLimit: 25,
47
47
  feeUpdateInterval: 60000,
48
- mempoolSpaceFeeInfoServer: 'https://mempool.space/api/v1/fees/recommended',
48
+ mempoolSpaceFeeInfoServer: 'https://mempool.space/api/v1/fees/precise',
49
49
  defaultFeeInfo: {
50
50
  lowFeeFudgeFactor: undefined,
51
51
  standardFeeLowFudgeFactor: undefined,
@@ -49,7 +49,7 @@ var _commonInfo = require('./commonInfo');
49
49
  gapLimit: 25,
50
50
  feeUpdateInterval: 60000,
51
51
  mempoolSpaceFeeInfoServer:
52
- 'https://mempool.space/testnet4/api/v1/fees/recommended',
52
+ 'https://mempool.space/testnet4/api/v1/fees/precise',
53
53
  defaultFeeInfo: {
54
54
  lowFeeFudgeFactor: undefined,
55
55
  standardFeeLowFudgeFactor: undefined,
@@ -32,7 +32,10 @@ const currencyInfo = {
32
32
  ..._commonInfo.legacyMemoInfo,
33
33
  defaultSettings: {
34
34
  customFeeSettings: ['satPerByte'],
35
- blockbookServers: ['wss://blockbook.fabien.cash/websocket'],
35
+ blockbookServers: [
36
+ 'wss://blockbook.fabien.cash/websocket',
37
+ 'wss://xec-blockbook.nownodes.io/wss/%{nowNodesApiKey}'
38
+ ],
36
39
  enableCustomServers: false
37
40
  },
38
41
  displayName: 'eCash',
@@ -18,9 +18,9 @@ const currencyInfo = {
18
18
  walletType: 'wallet:pivx',
19
19
 
20
20
  // Explorers:
21
- addressExplorer: 'https://zkbitcoin.com/address/%s',
22
- blockExplorer: 'https://zkbitcoin.com/block/%s',
23
- transactionExplorer: 'https://zkbitcoin.com/tx/%s',
21
+ addressExplorer: 'https://explorer.duddino.com/address/%s',
22
+ blockExplorer: 'https://explorer.duddino.com/block/%s',
23
+ transactionExplorer: 'https://explorer.duddino.com/tx/%s',
24
24
 
25
25
  denominations: [
26
26
  {
@@ -34,7 +34,7 @@ const currencyInfo = {
34
34
  ..._commonInfo.legacyMemoInfo,
35
35
  defaultSettings: {
36
36
  customFeeSettings: ['satPerByte'],
37
- blockbookServers: ['wss://pivx-eusa1.edge.app', 'wss://zkbitcoin.com'],
37
+ blockbookServers: ['wss://pivx-eusa1.edge.app'],
38
38
  enableCustomServers: false
39
39
  },
40
40
  displayName: 'PIVX',
@@ -8,6 +8,7 @@
8
8
 
9
9
 
10
10
  var _altcoinjs = require('altcoin-js');
11
+ var _psbtutils = require('altcoin-js/src/psbt/psbtutils');
11
12
  var _biggystring = require('biggystring');
12
13
  var _bip32 = require('bip32'); var bip32 = _interopRequireWildcard(_bip32);
13
14
  var _bip39 = require('bip39'); var bip39 = _interopRequireWildcard(_bip39);
@@ -967,6 +968,75 @@ function getBip137SegwitType(
967
968
  }
968
969
  }
969
970
 
971
+ /**
972
+ * The BIP-322 message hash: a BIP-340 style tagged hash of the raw message
973
+ * bytes. Unlike the legacy format there is no coin-specific prefix.
974
+ */
975
+ function bip322MessageHash(message) {
976
+ const tagHash = _altcoinjs.crypto.sha256(Buffer.from('BIP0322-signed-message', 'utf8'))
977
+ return _altcoinjs.crypto.sha256(
978
+ Buffer.concat([tagHash, tagHash, Buffer.from(message, 'utf8')])
979
+ )
980
+ } exports.bip322MessageHash = bip322MessageHash;
981
+
982
+ /**
983
+ * Signs `message` per BIP-322 and returns the witness stack that spends the
984
+ * virtual `to_spend` output locked to the key's address. Native SegWit
985
+ * (bip84) and nested SegWit (bip49) both spend through a P2WPKH witness; a
986
+ * nested address' redeem script is implied by the key, so verifiers rebuild
987
+ * the scriptSig from the witness. Returns `undefined` for legacy formats,
988
+ * which have no witness.
989
+ */
990
+ function signBip322Witness(
991
+ message,
992
+ keyPair,
993
+ format
994
+ ) {
995
+ if (format !== 'bip84' && format !== 'bip49') return undefined
996
+
997
+ const p2wpkh = _altcoinjs.payments.p2wpkh({ pubkey: keyPair.publicKey })
998
+ const scriptPubkey =
999
+ format === 'bip84'
1000
+ ? p2wpkh.output
1001
+ : _altcoinjs.payments.p2sh({ redeem: p2wpkh }).output
1002
+ // BIP-143 signs a P2WPKH input against the P2PKH script of its key hash.
1003
+ const scriptCode = _altcoinjs.payments.p2pkh({ hash: p2wpkh.hash }).output
1004
+ if (scriptPubkey == null || scriptCode == null) {
1005
+ throw new Error('Address could not sign message')
1006
+ }
1007
+
1008
+ const toSpend = new (0, _altcoinjs.Transaction)()
1009
+ toSpend.version = 0
1010
+ toSpend.locktime = 0
1011
+ toSpend.addInput(
1012
+ Buffer.alloc(32),
1013
+ 0xffffffff,
1014
+ 0,
1015
+ _altcoinjs.script.compile([_altcoinjs.opcodes.OP_0, bip322MessageHash(message)])
1016
+ )
1017
+ toSpend.addOutput(scriptPubkey, 0)
1018
+
1019
+ const toSign = new (0, _altcoinjs.Transaction)()
1020
+ toSign.version = 0
1021
+ toSign.locktime = 0
1022
+ toSign.addInput(toSpend.getHash(), 0, 0)
1023
+ toSign.addOutput(_altcoinjs.script.compile([_altcoinjs.opcodes.OP_RETURN]), 0)
1024
+
1025
+ const sighash = toSign.hashForWitnessV0(
1026
+ 0,
1027
+ scriptCode,
1028
+ 0,
1029
+ _altcoinjs.Transaction.SIGHASH_ALL
1030
+ )
1031
+ // Low-R grinding matches Bitcoin Core, so the output is byte-identical to
1032
+ // the signatures its `signmessage` and the BIP-322 test vectors produce.
1033
+ const signature = _altcoinjs.script.signature.encode(
1034
+ keyPair.sign(sighash, true),
1035
+ _altcoinjs.Transaction.SIGHASH_ALL
1036
+ )
1037
+ return [signature, keyPair.publicKey]
1038
+ }
1039
+
970
1040
  function signMessageBase64(
971
1041
  message,
972
1042
  privateKey,
@@ -979,6 +1049,14 @@ function getBip137SegwitType(
979
1049
  if (keyPair.privateKey == null) {
980
1050
  throw new Error('Address could not sign message')
981
1051
  }
1052
+ if (signatureFormat === 'bip322') {
1053
+ const witness = signBip322Witness(message, keyPair, format)
1054
+ // Legacy P2PKH addresses have no witness to sign with. BIP-322 has them
1055
+ // fall back to the legacy message format, which the Electrum encoding
1056
+ // below produces.
1057
+ if (witness != null)
1058
+ return _psbtutils.witnessStackToScriptWitness.call(void 0, witness).toString('base64')
1059
+ }
982
1060
  // BIP137 encodes the address type in the signature's header byte. Native
983
1061
  // SegWit (bip84 / P2WPKH) and nested SegWit (bip49 / P2SH-P2WPKH) each have
984
1062
  // their own header range; verifiers such as Bringin reject a SegWit address
@@ -8,8 +8,12 @@ var _types = require('edge-core-js/types');
8
8
  * about the script type.
9
9
  * - `bip137`: additionally encodes the signing address' script type, which
10
10
  * verifiers such as Bringin require for SegWit addresses.
11
+ * - `bip322`: a BIP-322 "simple" signature (the consensus-encoded witness
12
+ * of a virtual transaction spending from the address) for SegWit
13
+ * addresses. Legacy P2PKH addresses have no simple form, so BIP-322 has
14
+ * them sign in the legacy format, which is the `electrum` encoding.
11
15
  */
12
- const asUtxoSignatureFormat = _cleaners.asValue.call(void 0, 'electrum', 'bip137'); exports.asUtxoSignatureFormat = asUtxoSignatureFormat
16
+ const asUtxoSignatureFormat = _cleaners.asValue.call(void 0, 'electrum', 'bip137', 'bip322'); exports.asUtxoSignatureFormat = asUtxoSignatureFormat
13
17
 
14
18
 
15
19
 
@@ -6,7 +6,7 @@ var _utils = require('./utils'); var utils = _interopRequireWildcard(_utils);
6
6
  function accumulative(args) {
7
7
  const { utxos, targets, feeRate, changeScript } = args
8
8
 
9
- if (!isFinite(utils.uintOrNaN(feeRate))) {
9
+ if (!isFinite(utils.feeRateOrNaN(feeRate))) {
10
10
  throw new Error('No rate provided')
11
11
  }
12
12
 
@@ -24,7 +24,7 @@ var _utils = require('./utils'); var utils = _interopRequireWildcard(_utils);
24
24
  const utxo = utxos[i]
25
25
 
26
26
  // skip detrimental input
27
- const utxoFee = feeRate * utils.inputBytes(utxo)
27
+ const utxoFee = utils.feeForBytes(feeRate, utils.inputBytes(utxo))
28
28
  if (utxoFee > utxo.value) {
29
29
  if (i === utxos.length - 1) {
30
30
  break
@@ -37,7 +37,7 @@ var _utils = require('./utils'); var utils = _interopRequireWildcard(_utils);
37
37
  inValue += utxo.value
38
38
 
39
39
  const bytes = utils.transactionBytes(inputs, outputs)
40
- const fee = bytes * feeRate
40
+ const fee = utils.feeForBytes(feeRate, bytes)
41
41
 
42
42
  // go again?
43
43
  if (inValue < targetValue + fee) continue
@@ -47,7 +47,7 @@ var _utils = require('./utils'); var utils = _interopRequireWildcard(_utils);
47
47
 
48
48
  return {
49
49
  changeUsed: false,
50
- fee: Math.ceil(feeRate * utils.transactionBytes(inputs, outputs)),
50
+ fee: utils.feeForBytes(feeRate, utils.transactionBytes(inputs, outputs)),
51
51
  inputs
52
52
  }
53
53
  } exports.accumulative = accumulative;
@@ -7,7 +7,7 @@ var _utils = require('./utils'); var utils = _interopRequireWildcard(_utils);
7
7
  function forceUseUtxo(args) {
8
8
  const { utxos, useUtxos, targets, feeRate, changeScript } = args
9
9
 
10
- if (!isFinite(utils.uintOrNaN(feeRate))) {
10
+ if (!isFinite(utils.feeRateOrNaN(feeRate))) {
11
11
  throw new Error('No rate provided')
12
12
  }
13
13
 
@@ -21,7 +21,7 @@ var _utils = require('./utils'); var utils = _interopRequireWildcard(_utils);
21
21
  let inValue = inputs.reduce((n, { value }) => n + value, 0)
22
22
  const targetValue = utils.sumOrNaN(targets)
23
23
  const bytes = utils.transactionBytes(inputs, outputs)
24
- const fee = bytes * feeRate
24
+ const fee = utils.feeForBytes(feeRate, bytes)
25
25
  // if the new feeRate is already covered by lowering the change amount, return
26
26
  if (inValue >= targetValue + fee) {
27
27
  return utils.finalize(inputs, outputs, feeRate, changeScript)
@@ -31,7 +31,7 @@ var _utils = require('./utils'); var utils = _interopRequireWildcard(_utils);
31
31
  const utxo = utxos[i]
32
32
 
33
33
  // skip detrimental input
34
- const utxoFee = feeRate * utils.inputBytes(utxo)
34
+ const utxoFee = utils.feeForBytes(feeRate, utils.inputBytes(utxo))
35
35
  if (utxoFee > utxo.value) {
36
36
  if (i === utxos.length - 1) {
37
37
  break
@@ -44,7 +44,7 @@ var _utils = require('./utils'); var utils = _interopRequireWildcard(_utils);
44
44
  inValue += utxo.value
45
45
 
46
46
  const bytes = utils.transactionBytes(inputs, outputs)
47
- const fee = bytes * feeRate
47
+ const fee = utils.feeForBytes(feeRate, bytes)
48
48
 
49
49
  // go again?
50
50
  if (inValue < targetValue + fee) continue
@@ -54,7 +54,7 @@ var _utils = require('./utils'); var utils = _interopRequireWildcard(_utils);
54
54
 
55
55
  return {
56
56
  changeUsed: false,
57
- fee: Math.ceil(feeRate * utils.transactionBytes(inputs, outputs)),
57
+ fee: utils.feeForBytes(feeRate, utils.transactionBytes(inputs, outputs)),
58
58
  inputs
59
59
  }
60
60
  } exports.forceUseUtxo = forceUseUtxo;
@@ -10,7 +10,7 @@ var _utils = require('./utils'); var utils = _interopRequireWildcard(_utils);
10
10
  scriptPubkey: Buffer.from(target.script, 'hex')
11
11
  }))
12
12
 
13
- const fee = Math.ceil(feeRate * utils.transactionBytes(utxos, outputs))
13
+ const fee = utils.feeForBytes(feeRate, utils.transactionBytes(utxos, outputs))
14
14
  targets[0].value -= fee
15
15
  outputs[0].value -= fee
16
16
 
@@ -73,7 +73,7 @@ const scriptSizeMap = (() => {
73
73
  } exports.outputBytes = outputBytes;
74
74
 
75
75
  function dustThreshold(output, feeRate) {
76
- return outputBytes(output) * feeRate
76
+ return feeForBytes(feeRate, outputBytes(output))
77
77
  } exports.dustThreshold = dustThreshold;
78
78
 
79
79
  function witnessCount(inputs) {
@@ -122,6 +122,30 @@ function witnessCount(inputs) {
122
122
  return v
123
123
  } exports.uintOrNaN = uintOrNaN;
124
124
 
125
+ /**
126
+ * Validates a fee rate in satoshis per byte. Fee rates may be fractional,
127
+ * unlike the satoshi values they produce: a rate is always multiplied by a
128
+ * byte count and rounded up before it becomes a satoshi amount.
129
+ */
130
+ function feeRateOrNaN(v) {
131
+ if (!isFinite(v)) return NaN
132
+ if (v < 0) return NaN
133
+ return v
134
+ } exports.feeRateOrNaN = feeRateOrNaN;
135
+
136
+ /**
137
+ * Returns the whole number of satoshis required to pay for `bytes` at
138
+ * `feeRate` satoshis per byte. Always rounds up, so a fractional rate can
139
+ * never produce a fractional satoshi amount and can never underpay.
140
+ */
141
+ function feeForBytes(feeRate, bytes) {
142
+ // Multiplying a fractional rate carries floating point noise: `1.1 * 10` is
143
+ // 11.000000000000002, which would round an exact satoshi amount up to the
144
+ // next satoshi. Snap the product back to 12 significant digits first, which
145
+ // is far more precision than any real fee needs, but well short of the noise.
146
+ return Math.ceil(parseFloat((feeRate * bytes).toPrecision(12)))
147
+ } exports.feeForBytes = feeForBytes;
148
+
125
149
  function sumForgiving(range) {
126
150
  return range.reduce((a, x) => a + (isFinite(x.value) ? x.value : 0), 0)
127
151
  } exports.sumForgiving = sumForgiving;
@@ -139,7 +163,7 @@ function witnessCount(inputs) {
139
163
  const inValue = sumOrNaN(inputs)
140
164
  const outValue = sumOrNaN(outputs)
141
165
  let txSize = transactionBytes(inputs, outputs)
142
- let fee = Math.ceil(feeRate * txSize)
166
+ let fee = feeForBytes(feeRate, txSize)
143
167
 
144
168
  const changeValue = inValue - (outValue + fee)
145
169
  const changeOutput = {
@@ -149,7 +173,7 @@ function witnessCount(inputs) {
149
173
  }
150
174
  const changeOutputSize = outputBytes(changeOutput)
151
175
  txSize += changeOutputSize
152
- const changeFee = feeRate * changeOutputSize
176
+ const changeFee = feeForBytes(feeRate, changeOutputSize)
153
177
  changeOutput.value -= changeFee
154
178
  let changeUsed = false
155
179
  if (changeOutput.value > dustThreshold(changeOutput, feeRate)) {
@@ -1,7 +1,7 @@
1
1
  export declare const INFO_SERVER_URI = "https://info1.edge.app";
2
2
  export declare const FEES_PATH = "fees.json";
3
3
  export declare const MAX_FEE = 999999999;
4
- export declare const LOW_FEE = 2;
4
+ export declare const FEE_RATE_DECIMALS = 3;
5
5
  export declare const MAX_HIGH_DELAY = 200;
6
6
  export declare const MAX_STANDARD_DELAY = 6;
7
7
  export declare const MIN_STANDARD_DELAY = 2;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Calculate the sat/vB fee rate for a transaction that replaces another by fee:
3
+ * double the rate the replaced transaction paid.
4
+ *
5
+ * The rate rounds up to FEE_RATE_DECIMALS places. Rounding up means the
6
+ * replacement never pays less than double, and a positive rate never rounds
7
+ * down to zero, which the UTXO picker would accept as a valid rate. Rounding
8
+ * to a fixed number of places keeps the rate readable where it is reported as
9
+ * `feeRateUsed`.
10
+ *
11
+ * @param replacedFee The fee the replaced transaction paid, in satoshis
12
+ * @param replacedVBytes The replaced transaction's virtual size
13
+ * @returns {number}
14
+ */
15
+ export declare const calcReplacementFeeRate: (replacedFee: number, replacedVBytes: number) => number;
@@ -23,7 +23,7 @@ export interface UtxoTxOtherParams {
23
23
  export declare type UtxoSignMessageOtherParams = ReturnType<typeof asUtxoSignMessageOtherParams>;
24
24
  export declare const asUtxoSignMessageOtherParams: import("cleaners").ObjectCleaner<{
25
25
  publicAddress: string;
26
- signatureFormat: "electrum" | "bip137";
26
+ signatureFormat: "electrum" | "bip137" | "bip322";
27
27
  }>;
28
28
  export declare type UtxoSpendInfoOtherParams = ReturnType<typeof asUtxoSpendInfoOtherParams>;
29
29
  export declare const asUtxoSpendInfoOtherParams: import("cleaners").ObjectCleaner<{
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import { EdgeLog, EdgeMemo } from 'edge-core-js/types';
2
3
  import { ChangePath, CurrencyFormat, FeeInfo } from '../../plugin/types';
3
4
  import { UtxoData } from '../db/types';
@@ -203,6 +204,11 @@ export declare const xprivToPrivateKey: (args: XPrivToPrivateKeyArgs) => string;
203
204
  export declare function privateKeyToWIF(args: PrivateKeyToWIFArgs): string;
204
205
  export declare const wifToPrivateKeyEncoding: (args: WIFToPrivateKeyEncodingArgs) => PrivateKeyEncoding;
205
206
  export declare function privateKeyEncodingToPubkey(privateKeyEncoding: PrivateKeyEncoding): string;
207
+ /**
208
+ * The BIP-322 message hash: a BIP-340 style tagged hash of the raw message
209
+ * bytes. Unlike the legacy format there is no coin-specific prefix.
210
+ */
211
+ export declare function bip322MessageHash(message: string): Buffer;
206
212
  export declare function signMessageBase64(message: string, privateKey: string, format: CurrencyFormat, coin: string, signatureFormat?: UtxoSignatureFormat): string;
207
213
  export declare function makeTx(args: MakeTxArgs): MakeTxReturn;
208
214
  export declare function signTx(args: SignTxArgs): Promise<SignTxReturn>;
@@ -7,8 +7,12 @@ import { EdgeTokenId, InsufficientFundsError } from 'edge-core-js/types';
7
7
  * about the script type.
8
8
  * - `bip137`: additionally encodes the signing address' script type, which
9
9
  * verifiers such as Bringin require for SegWit addresses.
10
+ * - `bip322`: a BIP-322 "simple" signature (the consensus-encoded witness
11
+ * of a virtual transaction spending from the address) for SegWit
12
+ * addresses. Legacy P2PKH addresses have no simple form, so BIP-322 has
13
+ * them sign in the legacy format, which is the `electrum` encoding.
10
14
  */
11
- export declare const asUtxoSignatureFormat: Cleaner<"electrum" | "bip137">;
15
+ export declare const asUtxoSignatureFormat: Cleaner<"electrum" | "bip137" | "bip322">;
12
16
  export declare type UtxoSignatureFormat = ReturnType<typeof asUtxoSignatureFormat>;
13
17
  interface InsufficientFundsErrorOptsPlus {
14
18
  tokenId: EdgeTokenId;
@@ -7,6 +7,18 @@ export declare function dustThreshold(output: Output, feeRate: number): number;
7
7
  export declare function transactionBytes(inputs: UTXO[], outputs: Output[]): number;
8
8
  export declare const transactionSizeFromHex: (hex: string) => number;
9
9
  export declare function uintOrNaN(v: number): number;
10
+ /**
11
+ * Validates a fee rate in satoshis per byte. Fee rates may be fractional,
12
+ * unlike the satoshi values they produce: a rate is always multiplied by a
13
+ * byte count and rounded up before it becomes a satoshi amount.
14
+ */
15
+ export declare function feeRateOrNaN(v: number): number;
16
+ /**
17
+ * Returns the whole number of satoshis required to pay for `bytes` at
18
+ * `feeRate` satoshis per byte. Always rounds up, so a fractional rate can
19
+ * never produce a fractional satoshi amount and can never underpay.
20
+ */
21
+ export declare function feeForBytes(feeRate: number, bytes: number): number;
10
22
  export declare function sumForgiving(range: Array<{
11
23
  value: number;
12
24
  }>): number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edge-currency-plugins",
3
- "version": "3.13.0",
3
+ "version": "3.15.0",
4
4
  "description": "Edge currency plugins to fetch and process wallet accounts, balances and transactions.",
5
5
  "homepage": "https://edge.app/",
6
6
  "repository": "https://github.com/EdgeApp/edge-currency-plugins",