edge-currency-plugins 3.12.0 → 3.14.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/android/src/main/assets/edge-currency-plugins/edge-currency-plugins.js +48 -48
- package/lib/common/constants.js +4 -1
- package/lib/common/fees/calcMinerFeePerByte.js +19 -5
- package/lib/common/fees/calcReplacementFeeRate.js +25 -0
- package/lib/common/fees/processMempoolSpaceFees.js +12 -16
- package/lib/common/plugin/types.js +6 -0
- package/lib/common/utxobased/engine/ServerStates.js +196 -95
- package/lib/common/utxobased/engine/UtxoEngine.js +16 -3
- package/lib/common/utxobased/info/bitcoin.js +4 -4
- package/lib/common/utxobased/info/bitcoincash.js +1 -1
- package/lib/common/utxobased/info/bitcointestnet.js +1 -1
- package/lib/common/utxobased/info/bitcointestnet4.js +1 -1
- package/lib/common/utxobased/info/dash.js +1 -1
- package/lib/common/utxobased/info/digibyte.js +1 -1
- package/lib/common/utxobased/info/dogecoin.js +1 -1
- package/lib/common/utxobased/info/ecash.js +4 -1
- package/lib/common/utxobased/info/litecoin.js +3 -3
- package/lib/common/utxobased/info/pivx.js +5 -5
- package/lib/common/utxobased/info/qtum.js +1 -1
- package/lib/common/utxobased/info/vertcoin.js +1 -1
- package/lib/common/utxobased/info/zcoin.js +1 -1
- package/lib/common/utxobased/keymanager/utxopicker/accumulative.js +4 -4
- package/lib/common/utxobased/keymanager/utxopicker/forceUseUtxo.js +5 -5
- package/lib/common/utxobased/keymanager/utxopicker/subtractFee.js +1 -1
- package/lib/common/utxobased/keymanager/utxopicker/utils.js +27 -3
- package/lib/common/utxobased/network/Blockbook.js +10 -2
- package/lib/src/common/constants.d.ts +1 -1
- package/lib/src/common/fees/calcReplacementFeeRate.d.ts +15 -0
- package/lib/src/common/plugin/types.d.ts +7 -1
- package/lib/src/common/utxobased/engine/ServerStates.d.ts +21 -0
- package/lib/src/common/utxobased/keymanager/utxopicker/utils.d.ts +12 -0
- package/lib/test/common/fees/calcReplacementFeeRate.spec.d.ts +1 -0
- package/lib/test/common/fees/processMempoolSpaceFees.spec.d.ts +1 -0
- package/lib/test/common/utxobased/engine/ServerStates.spec.d.ts +1 -0
- package/lib/test/common/utxobased/keymanager/utxopicker/decimalFeeRate.spec.d.ts +1 -0
- package/lib/test/common/utxobased/keymanager/utxopicker/utils.spec.d.ts +1 -0
- package/package.json +1 -1
package/lib/common/constants.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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
|
|
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:
|
|
42
|
-
standardFeeLow:
|
|
43
|
-
standardFeeHigh: bs.
|
|
44
|
-
highFee: bs.
|
|
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
|
|
@@ -91,6 +91,47 @@ var _constants = require('./constants');
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* How long broadcastTx gives the first wave (our sockets and Edge's own HTTP
|
|
102
|
+
* servers) before it also sends the transaction to the NOWNodes HTTP servers.
|
|
103
|
+
* NOWNodes is a third party, so it should only see a transaction when our own
|
|
104
|
+
* infrastructure has not already carried it. A first wave that fails outright
|
|
105
|
+
* does not wait this long; the second wave fires as soon as it has all failed.
|
|
106
|
+
*/
|
|
107
|
+
const NOWNODES_BROADCAST_DELAY_MS = 2000; exports.NOWNODES_BROADCAST_DELAY_MS = NOWNODES_BROADCAST_DELAY_MS
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Every broadcast attempt is counted toward "all attempts failed", so every
|
|
111
|
+
* attempt must settle. Sockets already expire a request after 30 seconds
|
|
112
|
+
* (Socket.ts); HTTP has no such bound of its own, and a server that accepts
|
|
113
|
+
* the connection and never answers would otherwise hold the whole broadcast
|
|
114
|
+
* open. This mirrors the socket figure.
|
|
115
|
+
*/
|
|
116
|
+
const BROADCAST_ATTEMPT_TIMEOUT_MS = 30000; exports.BROADCAST_ATTEMPT_TIMEOUT_MS = BROADCAST_ATTEMPT_TIMEOUT_MS
|
|
117
|
+
|
|
118
|
+
const withTimeout = async (
|
|
119
|
+
promise,
|
|
120
|
+
ms,
|
|
121
|
+
message
|
|
122
|
+
) => {
|
|
123
|
+
let timer
|
|
124
|
+
const timeout = new Promise((resolve, reject) => {
|
|
125
|
+
timer = setTimeout(() => {
|
|
126
|
+
reject(new Error(message))
|
|
127
|
+
}, ms)
|
|
128
|
+
})
|
|
129
|
+
try {
|
|
130
|
+
return await Promise.race([promise, timeout])
|
|
131
|
+
} finally {
|
|
132
|
+
if (timer != null) clearTimeout(timer)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
94
135
|
|
|
95
136
|
function makeServerStates(config) {
|
|
96
137
|
const {
|
|
@@ -103,6 +144,7 @@ var _constants = require('./constants');
|
|
|
103
144
|
walletInfo
|
|
104
145
|
} = config
|
|
105
146
|
const { serverConfigs = [] } = pluginInfo.engineInfo
|
|
147
|
+
const { broadcastTimeoutMs = exports.BROADCAST_ATTEMPT_TIMEOUT_MS } = config
|
|
106
148
|
log('Making server states')
|
|
107
149
|
|
|
108
150
|
const serverStatesCache = {}
|
|
@@ -366,112 +408,171 @@ var _constants = require('./constants');
|
|
|
366
408
|
}
|
|
367
409
|
|
|
368
410
|
const instance = {
|
|
411
|
+
/**
|
|
412
|
+
* Broadcast in two waves:
|
|
413
|
+
*
|
|
414
|
+
* 1. Immediately: every blockbook in the connection cache (whether or not
|
|
415
|
+
* its socket has finished connecting; a queued request transmits as
|
|
416
|
+
* soon as the socket opens, and times out otherwise) and every
|
|
417
|
+
* `blockbook` HTTP server, which are Edge's own.
|
|
418
|
+
* 2. After NOWNODES_BROADCAST_DELAY_MS, or as soon as every first-wave
|
|
419
|
+
* attempt has failed, whichever comes first: the `blockbook-nownode`
|
|
420
|
+
* HTTP servers. These are a third party and only see the transaction
|
|
421
|
+
* when our own infrastructure has not already carried it.
|
|
422
|
+
*
|
|
423
|
+
* The first success wins. The promise rejects only after every attempt
|
|
424
|
+
* has failed. Earlier versions only used HTTP when no socket was
|
|
425
|
+
* connected, so a single socket that looked connected but never answered
|
|
426
|
+
* could fail the whole broadcast without any HTTP attempt.
|
|
427
|
+
*/
|
|
369
428
|
async broadcastTx(transaction) {
|
|
370
429
|
return await new Promise((resolve, reject) => {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
uri
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
})
|
|
395
|
-
.catch((e) => {
|
|
396
|
-
if (++bad === wsUris.length) {
|
|
397
|
-
const msg = e != null ? `With error ${e.message}` : ''
|
|
398
|
-
log.error(
|
|
399
|
-
`broadcastTx fail: ${JSON.stringify(transaction)}\n${msg}`
|
|
400
|
-
)
|
|
401
|
-
reject(e)
|
|
402
|
-
}
|
|
403
|
-
})
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
const httpAttempt = (
|
|
436
|
+
uri,
|
|
437
|
+
headers
|
|
438
|
+
) => ({
|
|
439
|
+
uri,
|
|
440
|
+
run: async () => {
|
|
441
|
+
const response = await io.fetchCors(`${uri}/api/v2/sendtx/`, {
|
|
442
|
+
method: 'POST',
|
|
443
|
+
headers,
|
|
444
|
+
body: transaction.signedTx
|
|
445
|
+
})
|
|
446
|
+
if (!response.ok) {
|
|
447
|
+
throw new Error(
|
|
448
|
+
`Failed to broadcast transaction via Blockbook: HTTP ${response.status}`
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
const json = await response.json()
|
|
452
|
+
return _blockbookApi.asBlockbookResponse.call(void 0, (0, _blockbookApi.asBroadcastTxResponse))(json).result
|
|
404
453
|
}
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
//
|
|
457
|
+
// Build both waves up front, so `attempts` is the full count and an
|
|
458
|
+
// all-fail first wave cannot reject before the second has run.
|
|
459
|
+
//
|
|
460
|
+
const firstWave = []
|
|
461
|
+
const secondWave = []
|
|
462
|
+
|
|
463
|
+
for (const uri of Object.keys(serverStatesCache)) {
|
|
464
|
+
const { blockbook } = serverStatesCache[uri]
|
|
465
|
+
if (blockbook == null) continue
|
|
466
|
+
firstWave.push({
|
|
467
|
+
uri,
|
|
468
|
+
run: async () => (await blockbook.broadcastTx(transaction)).result
|
|
469
|
+
})
|
|
405
470
|
}
|
|
406
471
|
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
472
|
+
// This is for the future when we want to get HTTP servers from the user
|
|
473
|
+
// settings:
|
|
474
|
+
// const httpUris = pluginState.getLocalServers(Infinity, [
|
|
475
|
+
// /^http(?:s)?:/i
|
|
476
|
+
// ])
|
|
477
|
+
const { nowNodesApiKey } = initOptions
|
|
478
|
+
for (const config of serverConfigs) {
|
|
479
|
+
if (config.type === 'blockbook-nownode') {
|
|
480
|
+
// NOWNodes requires the key, and the key must not go anywhere else:
|
|
481
|
+
if (nowNodesApiKey == null) {
|
|
482
|
+
log.warn(
|
|
483
|
+
'broadcastTx: skipping NOWNodes HTTP servers (no nowNodesApiKey)'
|
|
484
|
+
)
|
|
485
|
+
continue
|
|
486
|
+
}
|
|
487
|
+
for (const uri of config.uris) {
|
|
488
|
+
secondWave.push(httpAttempt(uri, { 'api-key': nowNodesApiKey }))
|
|
489
|
+
}
|
|
490
|
+
} else {
|
|
491
|
+
for (const uri of config.uris) {
|
|
492
|
+
firstWave.push(httpAttempt(uri, {}))
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const attempts = firstWave.length + secondWave.length
|
|
498
|
+
if (attempts === 0) {
|
|
499
|
+
reject(
|
|
500
|
+
new Error('No available connections. Check your internet signal.')
|
|
501
|
+
)
|
|
502
|
+
return
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let resolved = false
|
|
506
|
+
let failures = 0
|
|
507
|
+
const failureMessages = []
|
|
508
|
+
let secondWaveFired = false
|
|
509
|
+
let secondWaveTimer
|
|
510
|
+
|
|
511
|
+
const onSuccess = (uri, txid) => {
|
|
512
|
+
if (resolved) return
|
|
513
|
+
resolved = true
|
|
514
|
+
if (secondWaveTimer != null) clearTimeout(secondWaveTimer)
|
|
515
|
+
log(`broadcastTx succeeded via ${uri}: ${txid}`)
|
|
516
|
+
resolve(txid)
|
|
517
|
+
}
|
|
518
|
+
const onFailure = (uri, error) => {
|
|
519
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
520
|
+
failureMessages.push(`${uri}: ${message}`)
|
|
521
|
+
log.warn(`broadcastTx attempt failed for ${uri}: ${message}`)
|
|
522
|
+
failures++
|
|
523
|
+
if (resolved) return
|
|
524
|
+
if (failures === attempts) {
|
|
525
|
+
log.error(
|
|
526
|
+
`broadcastTx fail: ${JSON.stringify(
|
|
527
|
+
transaction
|
|
528
|
+
)}\n${failureMessages.join('\n')}`
|
|
529
|
+
)
|
|
427
530
|
reject(
|
|
428
|
-
|
|
531
|
+
error instanceof Error
|
|
532
|
+
? error
|
|
533
|
+
: new Error(`Broadcast failed: ${failureMessages.join('; ')}`)
|
|
429
534
|
)
|
|
430
535
|
return
|
|
431
536
|
}
|
|
432
|
-
|
|
433
|
-
//
|
|
434
|
-
if (
|
|
435
|
-
|
|
436
|
-
return
|
|
537
|
+
// Every first-wave attempt has failed, so there is nothing left to
|
|
538
|
+
// wait for. Fire the second wave now rather than sit out the delay.
|
|
539
|
+
if (!secondWaveFired && failures >= firstWave.length) {
|
|
540
|
+
fireSecondWave()
|
|
437
541
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
},
|
|
448
|
-
body: transaction.signedTx
|
|
542
|
+
}
|
|
543
|
+
const fire = (attempt) => {
|
|
544
|
+
withTimeout(
|
|
545
|
+
attempt.run(),
|
|
546
|
+
broadcastTimeoutMs,
|
|
547
|
+
`Timeout for broadcast to ${attempt.uri}`
|
|
548
|
+
)
|
|
549
|
+
.then(txid => {
|
|
550
|
+
onSuccess(attempt.uri, txid)
|
|
449
551
|
})
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
552
|
+
.catch((error) => {
|
|
553
|
+
onFailure(attempt.uri, error)
|
|
554
|
+
})
|
|
555
|
+
}
|
|
556
|
+
const fireSecondWave = () => {
|
|
557
|
+
if (secondWaveFired) return
|
|
558
|
+
secondWaveFired = true
|
|
559
|
+
if (secondWaveTimer != null) clearTimeout(secondWaveTimer)
|
|
560
|
+
if (secondWave.length === 0) return
|
|
561
|
+
log.warn(
|
|
562
|
+
'broadcastTx: first wave has not resolved, trying NOWNodes servers'
|
|
563
|
+
)
|
|
564
|
+
for (const attempt of secondWave) fire(attempt)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
for (const attempt of firstWave) fire(attempt)
|
|
568
|
+
if (firstWave.length === 0) {
|
|
569
|
+
// Nothing of our own to try first:
|
|
570
|
+
fireSecondWave()
|
|
571
|
+
} else if (secondWave.length > 0) {
|
|
572
|
+
secondWaveTimer = setTimeout(
|
|
573
|
+
fireSecondWave,
|
|
574
|
+
exports.NOWNODES_BROADCAST_DELAY_MS
|
|
575
|
+
)
|
|
475
576
|
}
|
|
476
577
|
})
|
|
477
578
|
},
|
|
@@ -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
|
|
191
|
+
// Double the fee rate used by the replaced transaction:
|
|
191
192
|
const vBytes = _utils.transactionSizeFromHex.call(void 0, replacedTx.hex)
|
|
192
|
-
const newFeeRate =
|
|
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
|
-
|
|
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) {
|
|
@@ -20,8 +20,8 @@ const currencyInfo = {
|
|
|
20
20
|
|
|
21
21
|
// Explorers:
|
|
22
22
|
blockExplorer: 'https://blockchair.com/bitcoin/block/%s',
|
|
23
|
-
addressExplorer: 'https://
|
|
24
|
-
transactionExplorer: 'https://
|
|
23
|
+
addressExplorer: 'https://mempool.space/address/%s',
|
|
24
|
+
transactionExplorer: 'https://mempool.space/tx/%s',
|
|
25
25
|
|
|
26
26
|
denominations: [
|
|
27
27
|
{ name: 'BTC', multiplier: '100000000', symbol: '₿' },
|
|
@@ -53,7 +53,7 @@ const currencyInfo = {
|
|
|
53
53
|
const engineInfo = {
|
|
54
54
|
serverConfigs: [
|
|
55
55
|
{
|
|
56
|
-
type: 'blockbook
|
|
56
|
+
type: 'blockbook',
|
|
57
57
|
uris: ['https://btc-wusa1.edge.app', 'https://btc-eu1.edge.app']
|
|
58
58
|
},
|
|
59
59
|
{
|
|
@@ -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/
|
|
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/
|
|
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/
|
|
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: [
|
|
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',
|
|
@@ -20,8 +20,8 @@ var _commonInfo = require('./commonInfo');
|
|
|
20
20
|
|
|
21
21
|
// Explorers:
|
|
22
22
|
blockExplorer: 'https://blockchair.com/litecoin/block/%s',
|
|
23
|
-
addressExplorer: 'https://
|
|
24
|
-
transactionExplorer: 'https://
|
|
23
|
+
addressExplorer: 'https://litecoinspace.org/address/%s',
|
|
24
|
+
transactionExplorer: 'https://litecoinspace.org/tx/%s',
|
|
25
25
|
|
|
26
26
|
denominations: [
|
|
27
27
|
{ name: 'LTC', multiplier: '100000000', symbol: 'Ł' },
|
|
@@ -50,7 +50,7 @@ var _commonInfo = require('./commonInfo');
|
|
|
50
50
|
const engineInfo = {
|
|
51
51
|
serverConfigs: [
|
|
52
52
|
{
|
|
53
|
-
type: 'blockbook
|
|
53
|
+
type: 'blockbook',
|
|
54
54
|
uris: ['https://ltc-wusa1.edge.app']
|
|
55
55
|
},
|
|
56
56
|
{
|