edge-currency-plugins 1.0.0 → 1.1.2

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.
@@ -116,24 +116,21 @@ const asServerListInfo = _cleaners.asObject.call(void 0, _cleaners.asEither.call
116
116
  const fetchServers = async () => {
117
117
  log(`${pluginId} - GET ${serverListInfoUrl}`)
118
118
 
119
- const response = await io.fetch(serverListInfoUrl)
120
- const responseBody = await (async () => {
121
- try {
122
- if (response.ok) {
123
- return await response.json()
124
- }
125
- log(
126
- `${pluginId} - Fetching ${serverListInfoUrl} failed with status ${response.status}`
127
- )
128
- } catch (err) {
129
- log(`${pluginId} - Fetching ${serverListInfoUrl} failed: ${err}`)
119
+ try {
120
+ const response = await io.fetch(serverListInfoUrl)
121
+ if (!response.ok) {
122
+ throw new Error(`Failed with status ${response.status}`)
130
123
  }
131
- return {}
132
- })()
133
-
134
- const serverListInfo = asServerListInfo(responseBody)
124
+ const responseBody = await response.json()
125
+ const serverListInfo = asServerListInfo(responseBody)
135
126
 
136
- return _nullishCoalesce(serverListInfo[currencyCode], () => ( []))
127
+ return _nullishCoalesce(serverListInfo[currencyCode], () => ( []))
128
+ } catch (error) {
129
+ log.warn(
130
+ `${pluginId} - GET ${serverListInfoUrl} failed: ${error.toString()}`
131
+ )
132
+ return []
133
+ }
137
134
  }
138
135
 
139
136
  const refreshServers = async () => {
@@ -1,4 +1,6 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _altcoinjs = require('altcoin-js');
2
+ var _bnjs = require('bn.js'); var _bnjs2 = _interopRequireDefault(_bnjs);
3
+
2
4
 
3
5
 
4
6
 
@@ -56,12 +58,11 @@ var _keymanager = require('../../keymanager/keymanager');
56
58
 
57
59
 
58
60
 
59
-
60
61
  const toEdgeTransaction = async (
61
62
  args
62
63
  ) => {
63
- const { tx, processor, walletTools, engineInfo } = args
64
-
64
+ const { tx, processor, walletTools, pluginInfo } = args
65
+ const { engineInfo, currencyInfo } = pluginInfo
65
66
  const ourReceiveAddresses = []
66
67
  for (const out of tx.ourOuts) {
67
68
  const { scriptPubkey } = tx.outputs[parseInt(out)]
@@ -95,14 +96,32 @@ var _keymanager = require('../../keymanager/keymanager');
95
96
  }
96
97
  }
97
98
 
99
+ const feeRes = {}
100
+
101
+ try {
102
+ const altcoinTx = _altcoinjs.Transaction.fromHex(tx.hex)
103
+ const vBytes = altcoinTx.virtualSize()
104
+ // feeRate must be of a number type (limited to 53 bits)
105
+ const feeRate = new (0, _bnjs2.default)(tx.fees, 10)
106
+ .div(new (0, _bnjs2.default)(vBytes, 10))
107
+ .toNumber()
108
+ Object.assign(feeRes, {
109
+ feeRateUsed: {
110
+ // GUI repo uses satPerVByte rather than satPerByte defined in customFeeTemplate
111
+ satPerVByte: feeRate
112
+ }
113
+ })
114
+ } catch (e) {}
115
+
98
116
  return {
99
- currencyCode: args.currencyCode,
100
- txid: args.tx.txid,
101
- blockHeight: args.tx.blockHeight,
102
- date: args.tx.date,
103
- nativeAmount: args.tx.ourAmount,
104
- networkFee: args.tx.fees,
105
- signedTx: args.tx.hex,
106
- ourReceiveAddresses
117
+ currencyCode: currencyInfo.currencyCode,
118
+ txid: tx.txid,
119
+ blockHeight: tx.blockHeight,
120
+ date: tx.date,
121
+ nativeAmount: tx.ourAmount,
122
+ networkFee: tx.fees,
123
+ signedTx: tx.hex,
124
+ ourReceiveAddresses,
125
+ ...feeRes
107
126
  }
108
127
  }; exports.toEdgeTransaction = toEdgeTransaction
@@ -371,7 +371,7 @@ var _constants = require('./constants');
371
371
  pluginState.serverScoreDown(uri)
372
372
  _optionalChain([deferredAddressSub, 'optionalAccess', _3 => _3.reject, 'call', _4 => _4()])
373
373
  })
374
- blockbook.watchAddresses(Array.from(addresses), deferred)
374
+ blockbook.watchAddresses(Array.from(serverState.addresses), deferred)
375
375
  }
376
376
 
377
377
  const watchBlocks = (uri) => {
@@ -66,7 +66,9 @@ var _utils = require('./utils');
66
66
  // each with a different list of custom servers.
67
67
  // The Edge UI only allows for one active login, though,
68
68
  // so this is OK for now:
69
- await pluginState.updateServers(userSettings)
69
+ pluginState.updateServers(userSettings).catch(err => {
70
+ log.error(err)
71
+ })
70
72
 
71
73
  const asCurrencyPrivateKey = _cleaners3.asPrivateKey.call(void 0, coinInfo.name, coinInfo.coinType)
72
74
  // Private key may be missing for watch-only wallets
@@ -253,10 +255,9 @@ var _utils = require('./utils');
253
255
  async (tx) =>
254
256
  await _ProcessorTransaction.toEdgeTransaction.call(void 0, {
255
257
  tx,
256
- currencyCode: currencyInfo.currencyCode,
257
258
  walletTools,
258
259
  processor,
259
- engineInfo
260
+ pluginInfo
260
261
  })
261
262
  )
262
263
  )
@@ -379,6 +380,7 @@ var _utils = require('./utils');
379
380
  targets,
380
381
  feeRate,
381
382
  coin: coinInfo.name,
383
+ currencyCode: currencyInfo.currencyCode,
382
384
  setRBF,
383
385
  freshChangeAddress,
384
386
  subtractFee
@@ -407,7 +409,7 @@ var _utils = require('./utils');
407
409
  nativeAmount = bs.sub(nativeAmount, networkFee)
408
410
 
409
411
  const otherParams = {
410
- txJson: { hex: tx.hex },
412
+ unsignedTx: tx.hex,
411
413
  psbt: {
412
414
  base64: tx.psbtBase64,
413
415
  inputs: tx.inputs,
@@ -469,8 +471,8 @@ var _utils = require('./utils');
469
471
  /*
470
472
  Get the wallet's UTXOs from the new transaction and save them to the processsor.
471
473
  */
472
- const utxos = await _getOwnUtxosFromTx.getOwnUtxosFromTx.call(void 0, engineInfo, processor, tx)
473
- await engineState.processUtxos(utxos)
474
+ const ownUtxos = await _getOwnUtxosFromTx.getOwnUtxosFromTx.call(void 0, engineInfo, processor, tx)
475
+ await engineState.processUtxos(ownUtxos)
474
476
  },
475
477
 
476
478
  async signTx(transaction) {
@@ -40,7 +40,6 @@ var _keymanager = require('../keymanager/keymanager');
40
40
 
41
41
 
42
42
 
43
-
44
43
  var _BlockBookAPI = require('../network/BlockBookAPI');
45
44
  var _Deferred = require('../network/Deferred'); var _Deferred2 = _interopRequireDefault(_Deferred);
46
45
 
@@ -386,22 +385,6 @@ var _utils3 = require('./utils');
386
385
  async broadcastTx(transaction) {
387
386
  const txId = await serverStates.broadcastTx(transaction)
388
387
 
389
- /*
390
- After successfully broadcasting the tx, we must mark each UTXO, used as
391
- an input for the tx, as spent so we don't try to reuse the UTXO.
392
- */
393
- const txInputs = _nullishCoalesce(_optionalChain([transaction, 'access', _ => _.otherParams, 'optionalAccess', _2 => _2.psbt, 'access', _3 => _3.inputs]), () => ( []))
394
- for (const input of txInputs) {
395
- const scriptPubkey = Buffer.from(input.scriptPubkey).toString('hex')
396
- const [utxo] = await processor.fetchUtxos({
397
- scriptPubkey
398
- })
399
- if (utxo != null) {
400
- utxo.spent = true
401
- await processor.saveUtxo(utxo)
402
- }
403
- }
404
-
405
388
  return txId
406
389
  },
407
390
  refillServers() {
@@ -447,18 +430,37 @@ var _utils3 = require('./utils');
447
430
  }
448
431
  },
449
432
  async processUtxos(utxos) {
433
+ // Map of scriptPubkey to IUTXOs
450
434
  const utxoMap = new Map()
435
+ const utxoIds = new Set()
436
+
437
+ // Map updated utxos
451
438
  for (const utxo of utxos) {
452
- const utxoSet = _nullishCoalesce(utxoMap.get(utxo.scriptPubkey), () => ( new Set()))
439
+ const utxoSet = _nullishCoalesce(utxoMap.get(utxo.scriptPubkey), () => ( []))
453
440
  if (!utxoMap.has(utxo.scriptPubkey))
454
441
  utxoMap.set(utxo.scriptPubkey, utxoSet)
455
- utxoSet.add(utxo)
442
+ // Add utxo to set for scriptPubkey
443
+ utxoSet.push(utxo)
444
+ // Add UTXO ID set
445
+ utxoIds.add(utxo.id)
456
446
  }
447
+
448
+ // Process UTXO sets for each scriptPubkey in map
457
449
  for (const [scriptPubkey, utxos] of utxoMap.entries()) {
450
+ // Get saved utxo set
451
+ const savedUtxos = await processor.fetchUtxos({ scriptPubkey })
452
+ // Filter UTXOs to de-duplicate (and undefined utxo as a type assert)
453
+ const filteredUtxos = savedUtxos.filter(
454
+ utxo => utxo != null && !utxoIds.has(_optionalChain([utxo, 'optionalAccess', _ => _.id]))
455
+ )
456
+
457
+ // Add updated utxos to utxo set
458
+ const combinedUtxos = [...filteredUtxos, ...utxos]
459
+
458
460
  await processProcessorUtxos({
459
461
  ...commonArgs,
460
462
  scriptPubkey,
461
- utxos
463
+ utxos: combinedUtxos
462
464
  })
463
465
  }
464
466
  }
@@ -647,20 +649,13 @@ const addToAddressTransactionCache = async (
647
649
  const transactionChanged = async (
648
650
  args
649
651
  ) => {
650
- const {
651
- emitter,
652
- walletTools,
653
- processor,
654
- pluginInfo: { currencyInfo, engineInfo },
655
- tx
656
- } = args
652
+ const { emitter, walletTools, processor, pluginInfo, tx } = args
657
653
  emitter.emit(_makeEngineEmitter.EngineEvent.TRANSACTIONS_CHANGED, [
658
654
  await _ProcessorTransaction.toEdgeTransaction.call(void 0, {
659
655
  tx,
660
- currencyCode: currencyInfo.currencyCode,
661
656
  walletTools,
662
657
  processor,
663
- engineInfo
658
+ pluginInfo
664
659
  })
665
660
  ])
666
661
  }; exports.transactionChanged = transactionChanged
@@ -1228,25 +1223,71 @@ const processProcessorUtxos = async (
1228
1223
  pluginInfo: { currencyInfo }
1229
1224
  } = args
1230
1225
 
1226
+ const updatedUtxos = Object.fromEntries(
1227
+ [...utxos].map(utxo => [utxo.id, utxo])
1228
+ )
1229
+ const utxoIdsToRemove = []
1231
1230
  const currentUtxos = await processor.fetchUtxos({ scriptPubkey })
1232
- const currentUtxoIds = []
1231
+
1232
+ //
1233
+ // Modify existing UTXO set
1234
+ //
1235
+
1233
1236
  let oldBalance = '0'
1234
1237
  for (const utxo of currentUtxos) {
1235
1238
  if (utxo == null)
1236
1239
  throw new Error(
1237
1240
  'Unexpected undefined utxo when processing unspent transactions'
1238
1241
  )
1239
- oldBalance = _biggystring.add.call(void 0, utxo.value, oldBalance)
1240
- currentUtxoIds.push(utxo.id)
1242
+ if (!utxo.spent) {
1243
+ // Accumulate over the current address balance
1244
+ oldBalance = _biggystring.add.call(void 0, utxo.value, oldBalance)
1245
+ }
1246
+
1247
+ // If the UTXO isn't present in the update UTXO set, then it is spent.
1248
+ if (updatedUtxos[utxo.id] == null) {
1249
+ if (utxo.blockHeight > 0) {
1250
+ // Delete the UTXO from the database if it's spent and confirmed
1251
+ utxoIdsToRemove.push(utxo.id)
1252
+ } else {
1253
+ // Otherwise just mark it as spent.
1254
+ updatedUtxos[utxo.id] = { ...utxo, spent: true }
1255
+ }
1256
+ } else {
1257
+ /*
1258
+ If the UTXO is present in the updated UTXO set, enforce that the UTXO
1259
+ remains spent in the new UTXO set. This means, we will never mark a UTXO
1260
+ as unspent again. UTXOs only become unspent again if the tx is dropped
1261
+ which shall be affected by another routine.
1262
+ */
1263
+ // TODO: To fix this logic for dropped-tx, add a condition which uses the
1264
+ // network's block-height to determine whether to set it back as unspend
1265
+ updatedUtxos[utxo.id].spent = utxo.spent || updatedUtxos[utxo.id].spent
1266
+ }
1241
1267
  }
1242
- await processor.removeUtxos(currentUtxoIds)
1268
+
1269
+ // Remove any spent UTXOs that have confirmations
1270
+ await processor.removeUtxos(utxoIdsToRemove)
1271
+
1272
+ //
1273
+ // Save updated UTXO set
1274
+ //
1243
1275
 
1244
1276
  let newBalance = '0'
1245
- for (const utxo of Array.from(utxos)) {
1246
- newBalance = _biggystring.add.call(void 0, utxo.value, newBalance)
1277
+ for (const utxo of Object.values(updatedUtxos)) {
1278
+ if (!utxo.spent) {
1279
+ // Accumulate over the new address balance
1280
+ newBalance = _biggystring.add.call(void 0, utxo.value, newBalance)
1281
+ }
1282
+ // Save new UTXOs
1247
1283
  await processor.saveUtxo(utxo)
1248
1284
  }
1249
1285
 
1286
+ //
1287
+ // Balance update
1288
+ //
1289
+
1290
+ // Address balance and emit address balance change event
1250
1291
  const diff = _biggystring.sub.call(void 0, newBalance, oldBalance)
1251
1292
  if (diff !== '0') {
1252
1293
  log('balance changed:', { scriptPubkey, diff })
@@ -1399,19 +1440,19 @@ const processRawUtxo = async (
1399
1440
  }
1400
1441
 
1401
1442
  const addToProcessorUtxoCache = (
1402
- processorUtxosCache,
1443
+ processorUtxoCache,
1403
1444
  path,
1404
1445
  scriptPubkey,
1405
1446
  requiredCount,
1406
1447
  utxo
1407
1448
  ) => {
1408
- const processorUtxos = _nullishCoalesce(processorUtxosCache[scriptPubkey], () => ( {
1409
- utxos: new Set(),
1449
+ const processorUtxos = _nullishCoalesce(processorUtxoCache[scriptPubkey], () => ( {
1450
+ utxos: [],
1410
1451
  processing: false,
1411
1452
  path,
1412
1453
  full: false
1413
1454
  }))
1414
- if (utxo != null) processorUtxos.utxos.add(utxo)
1415
- processorUtxosCache[scriptPubkey] = processorUtxos
1416
- processorUtxos.full = processorUtxos.utxos.size >= requiredCount
1455
+ if (utxo != null) processorUtxos.utxos.push(utxo)
1456
+ processorUtxoCache[scriptPubkey] = processorUtxos
1457
+ processorUtxos.full = processorUtxos.utxos.length >= requiredCount
1417
1458
  }
@@ -12,7 +12,29 @@ var _utils = require('../utils');
12
12
  processor,
13
13
  tx
14
14
  ) => {
15
- const utxos = []
15
+ const ownUtxos = []
16
+
17
+ //
18
+ // Spent UTXOs (Inputs)
19
+ //
20
+
21
+ const inputUtxoIds = tx.inputs.map(
22
+ input => `${input.txId}_${input.outputIndex}`
23
+ )
24
+ const inputUtxos = await processor.fetchUtxos({
25
+ utxoIds: inputUtxoIds
26
+ })
27
+ for (const utxo of inputUtxos) {
28
+ if (utxo == null) continue
29
+ // Must create a new IUTXO object when mutating processor objects because
30
+ // memlet may keep a reference in memory.
31
+ ownUtxos.push({ ...utxo, spent: true })
32
+ }
33
+
34
+ //
35
+ // Unspent UTXOs (Outputs)
36
+ //
37
+
16
38
  for (const output of tx.outputs) {
17
39
  const scriptPubkey = output.scriptPubkey
18
40
  const address = await processor.fetchAddress(scriptPubkey)
@@ -46,9 +68,10 @@ var _utils = require('../utils');
46
68
  blockHeight: tx.blockHeight,
47
69
  spent: false
48
70
  }
49
- utxos.push(utxo)
71
+ ownUtxos.push(utxo)
50
72
  }
51
73
  }
52
74
  }
53
- return utxos
75
+
76
+ return ownUtxos
54
77
  }; exports.getOwnUtxosFromTx = getOwnUtxosFromTx
@@ -236,4 +236,7 @@ var _keymanager = require('../keymanager/keymanager');
236
236
  }; exports.parsePathname = parsePathname
237
237
 
238
238
  const sumUtxos = (utxos) =>
239
- utxos.reduce((sum, { value }) => bs.add(sum, value), '0'); exports.sumUtxos = sumUtxos
239
+ utxos.reduce(
240
+ (sum, { spent, value }) => (spent ? sum : bs.add(sum, value)),
241
+ '0'
242
+ ); exports.sumUtxos = sumUtxos
@@ -16,7 +16,7 @@ var _constants = require('../../constants');
16
16
  // Configuration options:
17
17
  defaultSettings: {
18
18
  customFeeSettings: ['satPerByte'],
19
- blockbookServers: [],
19
+ blockbookServers: ['wss://blockbook.feathercoin.com'],
20
20
  enableCustomServers: false
21
21
  },
22
22
  customFeeTemplate: [
@@ -229,6 +229,7 @@ var VerifyAddressEnum; (function (VerifyAddressEnum) {
229
229
 
230
230
 
231
231
 
232
+
232
233
 
233
234
 
234
235
  function bip43PurposeNumberToTypeEnum(
@@ -933,7 +934,7 @@ const wifToPrivateKeyInternal = (
933
934
  changeScript
934
935
  })
935
936
  if (result.inputs == null || result.outputs == null) {
936
- throw new (0, _types.InsufficientFundsError)(args.coin)
937
+ throw new (0, _types.InsufficientFundsError)(args.currencyCode)
937
938
  }
938
939
 
939
940
  const sortedInputs = _bip69.sortInputs.call(void 0, result.inputs)
@@ -1,15 +1,14 @@
1
1
  import { EdgeTransaction } from 'edge-core-js/types';
2
- import { EngineInfo } from '../../../plugin/types';
2
+ import { PluginInfo } from '../../../plugin/types';
3
3
  import { UTXOPluginWalletTools } from '../../engine/makeUtxoWalletTools';
4
4
  import { Processor } from '../makeProcessor';
5
5
  import { IProcessorTransaction } from '../types';
6
6
  export declare const fromEdgeTransaction: (tx: EdgeTransaction) => IProcessorTransaction;
7
7
  interface ToEdgeTransactionArgs {
8
8
  tx: IProcessorTransaction;
9
- currencyCode: string;
10
9
  walletTools: UTXOPluginWalletTools;
11
10
  processor: Processor;
12
- engineInfo: EngineInfo;
11
+ pluginInfo: PluginInfo;
13
12
  }
14
13
  export declare const toEdgeTransaction: (args: ToEdgeTransactionArgs) => Promise<EdgeTransaction>;
15
14
  export {};
@@ -74,7 +74,7 @@ interface ProcessorUtxoCache {
74
74
  [key: string]: {
75
75
  processing: boolean;
76
76
  full: boolean;
77
- utxos: Set<IUTXO>;
77
+ utxos: IUTXO[];
78
78
  path: ChangePath;
79
79
  };
80
80
  }
@@ -6,9 +6,7 @@ export declare const asUtxoUserSettings: import("cleaners").ObjectCleaner<{
6
6
  }>;
7
7
  export declare type UtxoUserSettings = ReturnType<typeof asUtxoUserSettings>;
8
8
  export interface UtxoTxOtherParams {
9
- txJson?: {
10
- hex?: string;
11
- };
9
+ unsignedTx: string;
12
10
  psbt?: {
13
11
  base64: string;
14
12
  inputs: Input[];
@@ -128,6 +128,7 @@ export interface MakeTxArgs {
128
128
  feeRate: number;
129
129
  setRBF: boolean;
130
130
  coin: string;
131
+ currencyCode: string;
131
132
  freshChangeAddress: string;
132
133
  subtractFee?: boolean;
133
134
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edge-currency-plugins",
3
- "version": "1.0.0",
3
+ "version": "1.1.2",
4
4
  "description": "Edge currency plugins to fetch and process wallet accounts, balances and transactions.",
5
5
  "repository": "https://github.com/EdgeApp/edge-currency-plugins",
6
6
  "license": "MIT",
@@ -49,6 +49,7 @@
49
49
  "cleaners": "^0.3.12",
50
50
  "disklet": "^0.4.5",
51
51
  "edge-core-js": "^0.19.20",
52
+ "edge-sync-client": "^0.2.7",
52
53
  "memlet": "^0.1.6",
53
54
  "uri-js": "^4.4.0",
54
55
  "url-parse": "^1.5.2",