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.
Files changed (37) hide show
  1. package/android/src/main/assets/edge-currency-plugins/edge-currency-plugins.js +48 -48
  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/plugin/types.js +6 -0
  7. package/lib/common/utxobased/engine/ServerStates.js +196 -95
  8. package/lib/common/utxobased/engine/UtxoEngine.js +16 -3
  9. package/lib/common/utxobased/info/bitcoin.js +4 -4
  10. package/lib/common/utxobased/info/bitcoincash.js +1 -1
  11. package/lib/common/utxobased/info/bitcointestnet.js +1 -1
  12. package/lib/common/utxobased/info/bitcointestnet4.js +1 -1
  13. package/lib/common/utxobased/info/dash.js +1 -1
  14. package/lib/common/utxobased/info/digibyte.js +1 -1
  15. package/lib/common/utxobased/info/dogecoin.js +1 -1
  16. package/lib/common/utxobased/info/ecash.js +4 -1
  17. package/lib/common/utxobased/info/litecoin.js +3 -3
  18. package/lib/common/utxobased/info/pivx.js +5 -5
  19. package/lib/common/utxobased/info/qtum.js +1 -1
  20. package/lib/common/utxobased/info/vertcoin.js +1 -1
  21. package/lib/common/utxobased/info/zcoin.js +1 -1
  22. package/lib/common/utxobased/keymanager/utxopicker/accumulative.js +4 -4
  23. package/lib/common/utxobased/keymanager/utxopicker/forceUseUtxo.js +5 -5
  24. package/lib/common/utxobased/keymanager/utxopicker/subtractFee.js +1 -1
  25. package/lib/common/utxobased/keymanager/utxopicker/utils.js +27 -3
  26. package/lib/common/utxobased/network/Blockbook.js +10 -2
  27. package/lib/src/common/constants.d.ts +1 -1
  28. package/lib/src/common/fees/calcReplacementFeeRate.d.ts +15 -0
  29. package/lib/src/common/plugin/types.d.ts +7 -1
  30. package/lib/src/common/utxobased/engine/ServerStates.d.ts +21 -0
  31. package/lib/src/common/utxobased/keymanager/utxopicker/utils.d.ts +12 -0
  32. package/lib/test/common/fees/calcReplacementFeeRate.spec.d.ts +1 -0
  33. package/lib/test/common/fees/processMempoolSpaceFees.spec.d.ts +1 -0
  34. package/lib/test/common/utxobased/engine/ServerStates.spec.d.ts +1 -0
  35. package/lib/test/common/utxobased/keymanager/utxopicker/decimalFeeRate.spec.d.ts +1 -0
  36. package/lib/test/common/utxobased/keymanager/utxopicker/utils.spec.d.ts +1 -0
  37. package/package.json +1 -1
@@ -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',
@@ -44,7 +44,7 @@ const currencyInfo = {
44
44
  const engineInfo = {
45
45
  serverConfigs: [
46
46
  {
47
- type: 'blockbook-nownode',
47
+ type: 'blockbook',
48
48
  uris: ['https://pivx-wusa1.edge.app']
49
49
  },
50
50
  {
@@ -42,7 +42,7 @@ const currencyInfo = {
42
42
  const engineInfo = {
43
43
  serverConfigs: [
44
44
  {
45
- type: 'blockbook-nownode',
45
+ type: 'blockbook',
46
46
  uris: ['https://qtum-wusa1.edge.app']
47
47
  }
48
48
  ],
@@ -48,7 +48,7 @@ const currencyInfo = {
48
48
  const engineInfo = {
49
49
  serverConfigs: [
50
50
  {
51
- type: 'blockbook-nownode',
51
+ type: 'blockbook',
52
52
  uris: ['https://vtc-wusa1.edge.app']
53
53
  }
54
54
  ],
@@ -45,7 +45,7 @@ var _commonInfo = require('./commonInfo');
45
45
  const engineInfo = {
46
46
  serverConfigs: [
47
47
  {
48
- type: 'blockbook-nownode',
48
+ type: 'blockbook',
49
49
  uris: ['https://firo-eusa1.edge.app']
50
50
  },
51
51
  {
@@ -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)) {
@@ -177,8 +177,16 @@ var _Socket = require('./Socket');
177
177
 
178
178
 
179
179
  {
180
- const value = yield { ...message }
181
- deferred.resolve(value)
180
+ try {
181
+ const value = yield { ...message }
182
+ deferred.resolve(value)
183
+ } catch (error) {
184
+ // The socket reports a failed request (an error response, the
185
+ // request timeout, the socket closing) by throwing into this
186
+ // generator. Without this catch the deferred stays pending forever
187
+ // and the caller hangs instead of seeing the failure.
188
+ deferred.reject(error)
189
+ }
182
190
  return false
183
191
  }
184
192
  const generator = taskGeneratorFn()
@@ -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;
@@ -116,7 +116,13 @@ export interface EngineInfo {
116
116
  txSpecificHandling?: (tx: TransactionData, specialTx: unknown) => TransactionData;
117
117
  }
118
118
  export interface ServerConfig {
119
- type: 'blockbook-nownode';
119
+ /**
120
+ * - `blockbook`: a public Blockbook HTTP server. No credentials are sent.
121
+ * - `blockbook-nownode`: a NOWNodes Blockbook HTTP server. Requests carry
122
+ * the `nowNodesApiKey` init option as the `api-key` header, and the
123
+ * server is skipped when no key is configured.
124
+ */
125
+ type: 'blockbook' | 'blockbook-nownode';
120
126
  uris: string[];
121
127
  }
122
128
  /**
@@ -23,6 +23,11 @@ interface ServerStateConfig {
23
23
  pluginInfo: PluginInfo;
24
24
  pluginState: PluginState;
25
25
  walletInfo: SafeWalletInfo;
26
+ /**
27
+ * Upper bound on any single broadcast attempt. Defaults to the same 30
28
+ * seconds the socket layer uses for a request. Exposed for tests.
29
+ */
30
+ broadcastTimeoutMs?: number;
26
31
  }
27
32
  export interface ServerStates {
28
33
  setPickNextTaskCB: (callback: (uri: string) => AsyncGenerator<WsTask<unknown> | boolean>) => void;
@@ -46,5 +51,21 @@ export interface ServerStates {
46
51
  transactionSpecialQueryTask: (serverUri: string, txId: string) => WsTaskGenerator<unknown>;
47
52
  utxoListQueryTask: (serverUri: string, address: string) => WsTaskGenerator<AddressUtxosResponse>;
48
53
  }
54
+ /**
55
+ * How long broadcastTx gives the first wave (our sockets and Edge's own HTTP
56
+ * servers) before it also sends the transaction to the NOWNodes HTTP servers.
57
+ * NOWNodes is a third party, so it should only see a transaction when our own
58
+ * infrastructure has not already carried it. A first wave that fails outright
59
+ * does not wait this long; the second wave fires as soon as it has all failed.
60
+ */
61
+ export declare const NOWNODES_BROADCAST_DELAY_MS = 2000;
62
+ /**
63
+ * Every broadcast attempt is counted toward "all attempts failed", so every
64
+ * attempt must settle. Sockets already expire a request after 30 seconds
65
+ * (Socket.ts); HTTP has no such bound of its own, and a server that accepts
66
+ * the connection and never answers would otherwise hold the whole broadcast
67
+ * open. This mirrors the socket figure.
68
+ */
69
+ export declare const BROADCAST_ATTEMPT_TIMEOUT_MS = 30000;
49
70
  export declare function makeServerStates(config: ServerStateConfig): ServerStates;
50
71
  export {};
@@ -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.12.0",
3
+ "version": "3.14.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",