edge-currency-accountbased 0.7.72

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 (58) hide show
  1. package/CHANGELOG.md +713 -0
  2. package/LICENSE +29 -0
  3. package/README.md +63 -0
  4. package/index.js +3 -0
  5. package/lib/binance/bnbEngine.js +591 -0
  6. package/lib/binance/bnbInfo.js +43 -0
  7. package/lib/binance/bnbPlugin.js +168 -0
  8. package/lib/binance/bnbSchema.js +83 -0
  9. package/lib/binance/bnbTypes.js +39 -0
  10. package/lib/common/engine.js +918 -0
  11. package/lib/common/plugin.js +152 -0
  12. package/lib/common/schema.js +108 -0
  13. package/lib/common/types.js +85 -0
  14. package/lib/common/utils.js +378 -0
  15. package/lib/eos/eosEngine.js +1216 -0
  16. package/lib/eos/eosInfo.js +98 -0
  17. package/lib/eos/eosPlugin.js +314 -0
  18. package/lib/eos/eosSchema.js +190 -0
  19. package/lib/eos/eosTypes.js +88 -0
  20. package/lib/eos/telosInfo.js +94 -0
  21. package/lib/eos/waxInfo.js +95 -0
  22. package/lib/ethereum/etcInfo.js +121 -0
  23. package/lib/ethereum/ethEngine.js +832 -0
  24. package/lib/ethereum/ethInfo.js +1300 -0
  25. package/lib/ethereum/ethMiningFees.js +157 -0
  26. package/lib/ethereum/ethNetwork.js +2195 -0
  27. package/lib/ethereum/ethPlugin.js +377 -0
  28. package/lib/ethereum/ethSchema.js +61 -0
  29. package/lib/ethereum/ethTypes.js +461 -0
  30. package/lib/ethereum/ftminfo.js +102 -0
  31. package/lib/ethereum/rskInfo.js +101 -0
  32. package/lib/fio/fioConst.js +38 -0
  33. package/lib/fio/fioEngine.js +1250 -0
  34. package/lib/fio/fioError.js +38 -0
  35. package/lib/fio/fioInfo.js +72 -0
  36. package/lib/fio/fioPlugin.js +486 -0
  37. package/lib/fio/fioSchema.js +56 -0
  38. package/lib/index.js +44 -0
  39. package/lib/pluginError.js +32 -0
  40. package/lib/react-native/edge-currency-accountbased.js +239635 -0
  41. package/lib/react-native/edge-currency-accountbased.js.map +1 -0
  42. package/lib/react-native-io.js +41 -0
  43. package/lib/stellar/stellarEngine.js +563 -0
  44. package/lib/stellar/stellarInfo.js +37 -0
  45. package/lib/stellar/stellarPlugin.js +215 -0
  46. package/lib/stellar/stellarSchema.js +54 -0
  47. package/lib/stellar/stellarTypes.js +66 -0
  48. package/lib/tezos/tezosEngine.js +497 -0
  49. package/lib/tezos/tezosInfo.js +60 -0
  50. package/lib/tezos/tezosPlugin.js +174 -0
  51. package/lib/tezos/tezosTypes.js +110 -0
  52. package/lib/xrp/xrpEngine.js +583 -0
  53. package/lib/xrp/xrpInfo.js +47 -0
  54. package/lib/xrp/xrpPlugin.js +229 -0
  55. package/lib/xrp/xrpSchema.js +74 -0
  56. package/lib/xrp/xrpTypes.js +38 -0
  57. package/package.json +139 -0
  58. package/postinstall.sh +7 -0
@@ -0,0 +1,832 @@
1
+ /**
2
+ * Created by paul on 7/7/17.
3
+ */
4
+ //
5
+
6
+ import Common from '@ethereumjs/common'
7
+ import { Transaction } from '@ethereumjs/tx'
8
+ import { bns } from 'biggystring'
9
+ import { asMaybe } from 'cleaners'
10
+ import {
11
+
12
+
13
+
14
+
15
+
16
+
17
+ InsufficientFundsError
18
+ } from 'edge-core-js/types'
19
+ import abi from 'ethereumjs-abi'
20
+ import EthereumUtil from 'ethereumjs-util'
21
+ import ethWallet from 'ethereumjs-wallet'
22
+
23
+ import { CurrencyEngine } from '../common/engine.js'
24
+
25
+ import {
26
+ addHexPrefix,
27
+ bufToHex,
28
+ cleanTxLogs,
29
+ getEdgeInfoServer,
30
+ getOtherParams,
31
+ hexToDecimal,
32
+ isHex,
33
+ normalizeAddress,
34
+ toHex,
35
+ validateObject
36
+ } from '../common/utils'
37
+ import { calcMiningFee } from './ethMiningFees.js'
38
+ import { EthereumNetwork } from './ethNetwork'
39
+ import { EthereumPlugin } from './ethPlugin'
40
+ import { EthGasStationSchema } from './ethSchema.js'
41
+ import {
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+ asEthereumFees
51
+ } from './ethTypes.js'
52
+
53
+ const NETWORKFEES_POLL_MILLISECONDS = 60 * 10 * 1000 // 10 minutes
54
+ const ETH_GAS_STATION_WEI_MULTIPLIER = 100000000 // 100 million is the multiplier for ethgassstation because it uses 10x gwei
55
+ const WEI_MULTIPLIER = 1000000000
56
+ const GAS_PRICE_SANITY_CHECK = 30000 // 3000 Gwei (ethgasstation api reports gas prices with additional decimal place)
57
+
58
+ export class EthereumEngine extends CurrencyEngine {
59
+
60
+
61
+
62
+
63
+
64
+
65
+ constructor(
66
+ currencyPlugin,
67
+ walletInfo,
68
+ initOptions,
69
+ opts,
70
+ currencyInfo,
71
+ fetchCors
72
+ ) {
73
+ super(currencyPlugin, walletInfo, opts)
74
+ const { pluginId } = this.currencyInfo
75
+ if (typeof this.walletInfo.keys[`${pluginId}Key`] !== 'string') {
76
+ if (walletInfo.keys.keys && walletInfo.keys.keys[`${pluginId}Key`]) {
77
+ this.walletInfo.keys[`${pluginId}Key`] =
78
+ walletInfo.keys.keys[`${pluginId}Key`]
79
+ }
80
+ }
81
+ this.currencyPlugin = currencyPlugin
82
+ this.initOptions = initOptions
83
+ this.ethNetwork = new EthereumNetwork(this, this.currencyInfo)
84
+ this.lastEstimatedGasLimit = {
85
+ publicAddress: '',
86
+ contractAddress: '',
87
+ gasLimit: ''
88
+ }
89
+ this.fetchCors = fetchCors
90
+ }
91
+
92
+ updateBalance(tk, balance) {
93
+ if (typeof this.walletLocalData.totalBalances[tk] === 'undefined') {
94
+ this.walletLocalData.totalBalances[tk] = '0'
95
+ }
96
+ if (!bns.eq(balance, this.walletLocalData.totalBalances[tk])) {
97
+ this.walletLocalData.totalBalances[tk] = balance
98
+ this.log.warn(tk + ': token Address balance: ' + balance)
99
+ this.currencyEngineCallbacks.onBalanceChanged(tk, balance)
100
+ }
101
+ this.tokenCheckBalanceStatus[tk] = 1
102
+ this.updateOnAddressesChecked()
103
+ }
104
+
105
+ processUnconfirmedTransaction(tx) {
106
+ const fromAddress = '0x' + tx.inputs[0].addresses[0]
107
+ const toAddress = '0x' + tx.outputs[0].addresses[0]
108
+ const epochTime = Date.parse(tx.received) / 1000
109
+ const ourReceiveAddresses = []
110
+
111
+ let nativeAmount
112
+ if (
113
+ normalizeAddress(fromAddress) ===
114
+ normalizeAddress(this.walletLocalData.publicKey)
115
+ ) {
116
+ if (fromAddress === toAddress) {
117
+ // Spend to self
118
+ nativeAmount = bns.sub('0', tx.fees.toString(10))
119
+ } else {
120
+ nativeAmount = (0 - tx.total).toString(10)
121
+ nativeAmount = bns.sub(nativeAmount, tx.fees.toString(10))
122
+ }
123
+ } else {
124
+ nativeAmount = tx.total.toString(10)
125
+ ourReceiveAddresses.push(this.walletLocalData.publicKey)
126
+ }
127
+
128
+ const otherParams = {
129
+ from: [fromAddress],
130
+ to: [toAddress],
131
+ gas: '',
132
+ gasPrice: '',
133
+ gasUsed: tx.fees.toString(10),
134
+ cumulativeGasUsed: '',
135
+ errorVal: 0,
136
+ tokenRecipientAddress: null
137
+ }
138
+
139
+ const edgeTransaction = {
140
+ txid: addHexPrefix(tx.hash),
141
+ date: epochTime,
142
+ currencyCode: this.currencyInfo.currencyCode,
143
+ blockHeight: 0,
144
+ nativeAmount,
145
+ networkFee: tx.fees.toString(10),
146
+ ourReceiveAddresses,
147
+ signedTx: '',
148
+ otherParams
149
+ }
150
+ this.addTransaction(this.currencyInfo.currencyCode, edgeTransaction)
151
+ }
152
+
153
+ // curreently for Ethereum but should allow other currencies
154
+ async checkUpdateNetworkFees() {
155
+ // Get the network fees from the info server
156
+ try {
157
+ const infoServer = getEdgeInfoServer()
158
+ const url = `${infoServer}/v1/networkFees/${this.currencyInfo.currencyCode}`
159
+ const jsonObj = await this.ethNetwork.fetchGet(url)
160
+ const valid = asMaybe(asEthereumFees)(jsonObj) != null
161
+
162
+ if (valid) {
163
+ if (
164
+ JSON.stringify(this.walletLocalData.otherData.networkFees) !==
165
+ JSON.stringify(jsonObj)
166
+ ) {
167
+ this.walletLocalData.otherData.networkFees = jsonObj
168
+ this.walletLocalDataDirty = true
169
+ }
170
+ } else {
171
+ this.log.error(
172
+ `Error: Fetched invalid networkFees ${JSON.stringify(jsonObj)}`
173
+ )
174
+ }
175
+ } catch (err) {
176
+ this.log.error(
177
+ `Error fetching ${this.currencyInfo.currencyCode} networkFees from Edge info server`
178
+ )
179
+ this.log.error(err)
180
+ }
181
+
182
+ const { baseFeePerGas } = await this.ethNetwork.getBaseFeePerGas()
183
+
184
+ // If base fee is not suppported, update network fees fromethgasstation.info
185
+ if (baseFeePerGas == null) {
186
+ this.log.warn(`Updating networkFees from ethgasstation.info`)
187
+ this.updateNetworkFeesFromEthGasStation()
188
+ return
189
+ }
190
+
191
+ // Update the network fees from network base fee
192
+ this.updateNetworkFeesFromBaseFeePerGas(hexToDecimal(baseFeePerGas))
193
+ }
194
+
195
+ async updateNetworkFeesFromBaseFeePerGas(baseFeePerGas) {
196
+ /*
197
+ This algorithm calculates fee amounts using the base multiplier from the
198
+ info server.
199
+
200
+ Formula:
201
+ fee = baseMultiplier * baseFee + minPriorityFee
202
+
203
+ Where:
204
+ minPriorityFee = <minimum priority fee from info server>
205
+ baseFee = <latest block's base fee>
206
+ baseMultiplier = <multiplier from info server for low, standard, high, etc>
207
+
208
+ Reference analysis for choosing 2 gwei minimum priority fee:
209
+ https://hackmd.io/@q8X_WM2nTfu6nuvAzqXiTQ/1559-wallets#:~:text=2%20gwei%20is%20probably%20a%20very%20good%20default
210
+ */
211
+
212
+ const networkFees = this.walletLocalData.otherData.networkFees
213
+
214
+ // Make sure there is a default network fee entry and gasPrice entry
215
+ if (networkFees.default == null || networkFees.default.gasPrice == null) {
216
+ return
217
+ }
218
+
219
+ const defaultNetworkFee =
220
+ this.currencyInfo.defaultSettings.otherSettings.defaultNetworkFees.default
221
+
222
+ // The minimum priority fee for slow transactions
223
+ const minPriorityFee =
224
+ networkFees.default.minPriorityFee || defaultNetworkFee.minPriorityFee
225
+ // This is how much we will multiply the base fee by
226
+ const baseMultiplier =
227
+ networkFees.default.baseFeeMultiplier ||
228
+ defaultNetworkFee.baseFeeMultiplier
229
+
230
+ // Make sure the properties exist
231
+ if (minPriorityFee == null || baseMultiplier == null) {
232
+ return
233
+ }
234
+
235
+ const { gasPrice } = networkFees.default
236
+
237
+ const formula = (baseMultiplier) =>
238
+ bns.div(
239
+ bns.add(bns.mul(baseMultiplier, baseFeePerGas), minPriorityFee),
240
+ '1'
241
+ )
242
+
243
+ // Update default network fees
244
+ networkFees.default = {
245
+ ...networkFees.default,
246
+ gasPrice: {
247
+ ...gasPrice,
248
+ ...Object.keys(baseMultiplier).reduce(
249
+ (fees, key) => ({ ...fees, [key]: formula(baseMultiplier[key]) }),
250
+ {}
251
+ )
252
+ }
253
+ }
254
+ }
255
+
256
+ // deprecate after london hardfork because of EIP 1559
257
+ async updateNetworkFeesFromEthGasStation() {
258
+ let jsonObj
259
+ try {
260
+ const { ethGasStationUrl } =
261
+ this.currencyInfo.defaultSettings.otherSettings
262
+ if (ethGasStationUrl == null) return
263
+ const { ethGasStationApiKey } = this.initOptions
264
+ jsonObj = await this.ethNetwork.fetchGet(
265
+ `${ethGasStationUrl}?api-key=${ethGasStationApiKey || ''}`
266
+ )
267
+ const valid = validateObject(jsonObj, EthGasStationSchema)
268
+
269
+ if (valid) {
270
+ const fees = this.walletLocalData.otherData.networkFees
271
+ const ethereumFee = fees.default
272
+ if (!ethereumFee.gasPrice) {
273
+ return
274
+ }
275
+ const gasPrice = ethereumFee.gasPrice
276
+
277
+ const safeLow = jsonObj.safeLow
278
+ let average = jsonObj.average
279
+ let fast = jsonObj.fast
280
+ let fastest = jsonObj.fastest
281
+
282
+ // Sanity checks
283
+ if (safeLow <= 0 || safeLow > GAS_PRICE_SANITY_CHECK) {
284
+ throw new Error('Invalid safeLow value from EthGasStation')
285
+ }
286
+ if (average < 1 || average > GAS_PRICE_SANITY_CHECK) {
287
+ throw new Error('Invalid average value from EthGasStation')
288
+ }
289
+ if (fast < 1 || fast > GAS_PRICE_SANITY_CHECK) {
290
+ throw new Error('Invalid fastest value from EthGasStation')
291
+ }
292
+ if (fastest < 1 || fastest > GAS_PRICE_SANITY_CHECK) {
293
+ throw new Error('Invalid fastest value from EthGasStation')
294
+ }
295
+
296
+ // Correct inconsistencies
297
+ if (average <= safeLow) average = safeLow + 1
298
+ if (fast <= average) fast = average + 1
299
+ if (fastest <= fast) fastest = fast + 1
300
+
301
+ let lowFee = safeLow
302
+ let standardFeeLow = fast
303
+ let standardFeeHigh = (fast + fastest) * 0.75
304
+ let highFee = fastest
305
+
306
+ lowFee = (
307
+ Math.round(lowFee) * ETH_GAS_STATION_WEI_MULTIPLIER
308
+ ).toString()
309
+ standardFeeLow = (
310
+ Math.round(standardFeeLow) * ETH_GAS_STATION_WEI_MULTIPLIER
311
+ ).toString()
312
+ standardFeeHigh = (
313
+ Math.round(standardFeeHigh) * ETH_GAS_STATION_WEI_MULTIPLIER
314
+ ).toString()
315
+ highFee = (
316
+ Math.round(highFee) * ETH_GAS_STATION_WEI_MULTIPLIER
317
+ ).toString()
318
+
319
+ if (
320
+ gasPrice.lowFee !== lowFee ||
321
+ gasPrice.standardFeeLow !== standardFeeLow ||
322
+ gasPrice.highFee !== highFee ||
323
+ gasPrice.standardFeeHigh !== standardFeeHigh
324
+ ) {
325
+ gasPrice.lowFee = lowFee
326
+ gasPrice.standardFeeLow = standardFeeLow
327
+ gasPrice.highFee = highFee
328
+ gasPrice.standardFeeHigh = standardFeeHigh
329
+ this.walletLocalDataDirty = true
330
+ }
331
+ } else {
332
+ throw new Error(`Error: Fetched invalid networkFees from EthGasStation`)
333
+ }
334
+ } catch (err) {
335
+ this.log.error(
336
+ `Error fetching ${this.currencyInfo.currencyCode} networkFees from EthGasStation`
337
+ )
338
+ this.log.error(err)
339
+ this.log.crash(err, { rawData: jsonObj })
340
+ }
341
+ }
342
+
343
+ async clearBlockchainCache() {
344
+ await super.clearBlockchainCache()
345
+ this.otherData.nextNonce = '0'
346
+ this.otherData.unconfirmedNextNonce = '0'
347
+ }
348
+
349
+ // ****************************************************************************
350
+ // Public methods
351
+ // ****************************************************************************
352
+
353
+ async startEngine() {
354
+ this.engineOn = true
355
+ this.addToLoop('checkUpdateNetworkFees', NETWORKFEES_POLL_MILLISECONDS)
356
+
357
+ this.ethNetwork.needsLoop()
358
+
359
+ super.startEngine()
360
+ }
361
+
362
+ async resyncBlockchain() {
363
+ await this.killEngine()
364
+ await this.clearBlockchainCache()
365
+ await this.startEngine()
366
+ }
367
+
368
+ async makeSpend(edgeSpendInfoIn) {
369
+ const { edgeSpendInfo, currencyCode } = super.makeSpend(edgeSpendInfoIn)
370
+
371
+ /**
372
+ For RBF transactions, get the gas price and limit (fees) of the existing
373
+ transaction as well as the current nonce. The fees and the nonce will be
374
+ used instead of the calculated equivalents.
375
+ */
376
+ let rbfGasPrice
377
+ let rbfGasLimit
378
+ let rbfNonce
379
+ const rbfTxid =
380
+ edgeSpendInfo.rbfTxid && normalizeAddress(edgeSpendInfo.rbfTxid)
381
+ if (rbfTxid) {
382
+ const rbfTxIndex = this.findTransaction(currencyCode, rbfTxid)
383
+
384
+ if (rbfTxIndex > -1) {
385
+ const rbfTrx = this.transactionList[currencyCode][rbfTxIndex]
386
+
387
+ if (rbfTrx.otherParams) {
388
+ const { gasPrice, gas, nonceUsed } = rbfTrx.otherParams
389
+ rbfGasPrice = bns.mul(gasPrice, '2')
390
+ rbfGasLimit = gas
391
+ rbfNonce = nonceUsed
392
+ }
393
+ }
394
+
395
+ if (!rbfGasPrice || !rbfGasLimit || !rbfNonce) {
396
+ throw new Error('Missing data to complete RBF transaction.')
397
+ }
398
+ }
399
+
400
+ // Ethereum can only have one output
401
+ if (edgeSpendInfo.spendTargets.length !== 1) {
402
+ throw new Error('Error: only one output allowed')
403
+ }
404
+
405
+ const spendTarget = edgeSpendInfo.spendTargets[0]
406
+ const publicAddress = spendTarget.publicAddress
407
+ if (!EthereumUtil.isValidAddress(publicAddress)) {
408
+ throw new TypeError(`Invalid ${this.currencyInfo.pluginId} address`)
409
+ }
410
+
411
+ let data =
412
+ spendTarget.otherParams != null ? spendTarget.otherParams.data : undefined
413
+
414
+ let otherParams = {}
415
+
416
+ let gasPrice
417
+ let gasLimit
418
+ let useDefaults = false
419
+
420
+ // Use RBF gas price and gas limit when present, otherwise, calculate mining fees
421
+ if (rbfGasPrice && rbfGasLimit) {
422
+ gasPrice = rbfGasPrice
423
+ gasLimit = rbfGasLimit
424
+ } else {
425
+ const miningFees = calcMiningFee(
426
+ edgeSpendInfo,
427
+ this.walletLocalData.otherData.networkFees,
428
+ this.currencyInfo
429
+ )
430
+ gasPrice = miningFees.gasPrice
431
+ gasLimit = miningFees.gasLimit
432
+ useDefaults = miningFees.useDefaults
433
+ }
434
+
435
+ const defaultGasLimit = gasLimit
436
+ let nativeAmount = edgeSpendInfo.spendTargets[0].nativeAmount
437
+
438
+ let contractAddress
439
+ let value
440
+ if (currencyCode === this.currencyInfo.currencyCode) {
441
+ const ethParams = {
442
+ from: [this.walletLocalData.publicKey],
443
+ to: [publicAddress],
444
+ gas: gasLimit,
445
+ gasPrice: gasPrice,
446
+ gasUsed: '0',
447
+ cumulativeGasUsed: '0',
448
+ errorVal: 0,
449
+ tokenRecipientAddress: null,
450
+ nonceArg: rbfNonce,
451
+ rbfTxid,
452
+ data
453
+ }
454
+ otherParams = ethParams
455
+ value = bns.add(nativeAmount, '0', 16)
456
+ } else {
457
+ if (data) {
458
+ contractAddress = publicAddress
459
+ } else {
460
+ const tokenInfo = this.getTokenInfo(currencyCode)
461
+ if (!tokenInfo || typeof tokenInfo.contractAddress !== 'string') {
462
+ throw new Error(
463
+ 'Error: Token not supported or invalid contract address'
464
+ )
465
+ }
466
+
467
+ contractAddress = tokenInfo.contractAddress
468
+ value = '0x0'
469
+ }
470
+
471
+ const ethParams = {
472
+ from: [this.walletLocalData.publicKey],
473
+ to: [contractAddress],
474
+ gas: gasLimit,
475
+ gasPrice: gasPrice,
476
+ gasUsed: '0',
477
+ cumulativeGasUsed: '0',
478
+ errorVal: 0,
479
+ tokenRecipientAddress: publicAddress,
480
+ nonceArg: rbfNonce,
481
+ rbfTxid,
482
+ data
483
+ }
484
+ otherParams = ethParams
485
+ }
486
+
487
+ // If the recipient or contractaddress has changed from previous makeSpend(), calculate the gasLimit
488
+ if (
489
+ useDefaults &&
490
+ (this.lastEstimatedGasLimit.publicAddress !== publicAddress ||
491
+ this.lastEstimatedGasLimit.contractAddress !== contractAddress ||
492
+ this.lastEstimatedGasLimit.gasLimit === '')
493
+ ) {
494
+ if (!data) {
495
+ const dataArray = abi.simpleEncode(
496
+ 'transfer(address,uint256):(uint256)',
497
+ contractAddress || publicAddress,
498
+ value
499
+ )
500
+ data = '0x' + Buffer.from(dataArray).toString('hex')
501
+ }
502
+
503
+ const estimateGasParams = [
504
+ {
505
+ to: contractAddress || publicAddress,
506
+ from: this.walletLocalData.publicKey,
507
+ gas: '0xffffff',
508
+ value,
509
+ data
510
+ },
511
+ 'latest'
512
+ ]
513
+ try {
514
+ // Determine if recipient is a normal or contract address
515
+ const getCodeResult = await this.ethNetwork.multicastServers(
516
+ 'eth_getCode',
517
+ [contractAddress || publicAddress, 'latest']
518
+ )
519
+
520
+ try {
521
+ if (getCodeResult.result.result !== '0x') {
522
+ const estimateGasResult = await this.ethNetwork.multicastServers(
523
+ 'eth_estimateGas',
524
+ estimateGasParams
525
+ )
526
+ this.log.warn(
527
+ 'lookhere estimateGas estimateGasResult',
528
+ JSON.stringify(estimateGasResult)
529
+ )
530
+ gasLimit = bns.add(
531
+ parseInt(estimateGasResult.result.result, 16).toString(),
532
+ '0'
533
+ )
534
+ // Overestimate gas limit to reduce chance of failure when sending to a contract
535
+ if (currencyCode === this.currencyInfo.currencyCode) {
536
+ // Double gas limit estimate when sending ETH to contract
537
+ gasLimit = bns.mul(gasLimit, '2')
538
+ } else {
539
+ // For tokens, double estimate if it's less than half of default, otherwise use default. For estimates beyond default value, use the estimate as-is.
540
+ gasLimit = bns.lt(gasLimit, bns.div(defaultGasLimit, '2'))
541
+ ? bns.mul(gasLimit, '2')
542
+ : bns.lt(gasLimit, defaultGasLimit)
543
+ ? defaultGasLimit
544
+ : gasLimit
545
+ }
546
+ } else {
547
+ gasLimit = '21000'
548
+ }
549
+ } catch (e) {
550
+ // If we know the address is a contract but estimateGas fails use the default token gas limit
551
+ if (
552
+ this.currencyInfo.defaultSettings.otherSettings.defaultNetworkFees
553
+ .default.gasLimit.tokenTransaction != null
554
+ )
555
+ gasLimit =
556
+ this.currencyInfo.defaultSettings.otherSettings.defaultNetworkFees
557
+ .default.gasLimit.tokenTransaction
558
+ }
559
+
560
+ // Sanity check calculated value
561
+ if (bns.lt(gasLimit, '21000')) {
562
+ gasLimit = defaultGasLimit
563
+ this.lastEstimatedGasLimit.gasLimit = ''
564
+ throw new Error('Calculated gasLimit less than minimum')
565
+ }
566
+
567
+ // Save locally to compare for future makeSpend() calls
568
+ this.lastEstimatedGasLimit = {
569
+ publicAddress,
570
+ contractAddress,
571
+ gasLimit
572
+ }
573
+ } catch (err) {
574
+ this.log.error(`makeSpend Error determining gas limit ${err}`)
575
+ }
576
+ } else if (useDefaults) {
577
+ // If recipient and contract address are the same from the previous makeSpend(), use the previously calculated gasLimit
578
+ gasLimit = this.lastEstimatedGasLimit.gasLimit
579
+ }
580
+ otherParams.gas = gasLimit
581
+
582
+ const nativeBalance =
583
+ this.walletLocalData.totalBalances[this.currencyInfo.currencyCode]
584
+
585
+ let nativeNetworkFee = bns.mul(gasPrice, gasLimit)
586
+ let totalTxAmount = '0'
587
+ let parentNetworkFee = null
588
+
589
+ if (currencyCode === this.currencyInfo.currencyCode) {
590
+ totalTxAmount = bns.add(nativeNetworkFee, nativeAmount)
591
+ if (bns.gt(totalTxAmount, nativeBalance)) {
592
+ throw new InsufficientFundsError()
593
+ }
594
+ nativeAmount = bns.mul(totalTxAmount, '-1')
595
+ } else {
596
+ parentNetworkFee = nativeNetworkFee
597
+ // Check if there's enough parent currency to pay the transaction fee, and if not return the parent currency code and amount
598
+ if (bns.gt(nativeNetworkFee, nativeBalance)) {
599
+ throw new InsufficientFundsError({
600
+ currencyCode: this.currencyInfo.currencyCode,
601
+ networkFee: nativeNetworkFee
602
+ })
603
+ }
604
+ const balanceToken = this.walletLocalData.totalBalances[currencyCode]
605
+ if (bns.gt(nativeAmount, balanceToken)) {
606
+ throw new InsufficientFundsError()
607
+ }
608
+ nativeNetworkFee = '0' // Do not show a fee for token transactions.
609
+ nativeAmount = bns.mul(nativeAmount, '-1')
610
+ }
611
+ // **********************************
612
+ // Create the unsigned EdgeTransaction
613
+
614
+ // This is used for display purposes in the GUI
615
+ const feeRateUsed = {
616
+ // Convert gasPrice from wei to gwei
617
+ gasPrice: bns.div(
618
+ gasPrice,
619
+ WEI_MULTIPLIER.toString(),
620
+ WEI_MULTIPLIER.toString().length - 1
621
+ ),
622
+ gasLimit
623
+ }
624
+
625
+ const edgeTransaction = {
626
+ txid: '', // txid
627
+ date: 0, // date
628
+ currencyCode, // currencyCode
629
+ blockHeight: 0, // blockHeight
630
+ nativeAmount, // nativeAmount
631
+ networkFee: nativeNetworkFee, // networkFee
632
+ feeRateUsed,
633
+ ourReceiveAddresses: [], // ourReceiveAddresses
634
+ signedTx: '', // signedTx
635
+ otherParams // otherParams
636
+ }
637
+
638
+ if (parentNetworkFee) {
639
+ edgeTransaction.parentNetworkFee = parentNetworkFee
640
+ }
641
+
642
+ return edgeTransaction
643
+ }
644
+
645
+ async signTx(edgeTransaction) {
646
+ const otherParams = getOtherParams(edgeTransaction)
647
+
648
+ // Do signing
649
+ const gasLimitHex = toHex(otherParams.gas)
650
+ const gasPriceHex = toHex(otherParams.gasPrice)
651
+ let nativeAmountHex
652
+
653
+ if (edgeTransaction.currencyCode === this.currencyInfo.currencyCode) {
654
+ // Remove the networkFee from the nativeAmount
655
+ const nativeAmount = bns.add(
656
+ edgeTransaction.nativeAmount,
657
+ edgeTransaction.networkFee
658
+ )
659
+ nativeAmountHex = bns.mul('-1', nativeAmount, 16)
660
+ } else {
661
+ nativeAmountHex = bns.mul('-1', edgeTransaction.nativeAmount, 16)
662
+ }
663
+
664
+ // Nonce:
665
+
666
+ const nonceArg = otherParams.nonceArg
667
+ let nonce = nonceArg
668
+ if (!nonce) {
669
+ // Use an unconfirmed nonce if
670
+ // 1. We have unconfirmed spending txs in the transaction list
671
+ // 2. It is greater than the confirmed nonce
672
+ // 3. Is no more than 5 higher than confirmed nonce
673
+ // Othewise, use the next nonce
674
+ if (
675
+ this.walletLocalData.numUnconfirmedSpendTxs &&
676
+ bns.gt(
677
+ this.walletLocalData.otherData.unconfirmedNextNonce,
678
+ this.walletLocalData.otherData.nextNonce
679
+ )
680
+ ) {
681
+ const diff = bns.sub(
682
+ this.walletLocalData.otherData.unconfirmedNextNonce,
683
+ this.walletLocalData.otherData.nextNonce
684
+ )
685
+ if (bns.lte(diff, '5')) {
686
+ nonce = this.walletLocalData.otherData.unconfirmedNextNonce
687
+ this.walletLocalData.otherData.unconfirmedNextNonce = bns.add(
688
+ this.walletLocalData.otherData.unconfirmedNextNonce,
689
+ '1'
690
+ )
691
+ this.walletLocalDataDirty = true
692
+ } else {
693
+ const e = new Error('Excessive pending spend transactions')
694
+ e.name = 'ErrorExcessivePendingSpends'
695
+ throw e
696
+ }
697
+ } else {
698
+ nonce = this.walletLocalData.otherData.nextNonce
699
+ this.walletLocalData.otherData.unconfirmedNextNonce = bns.add(
700
+ this.walletLocalData.otherData.nextNonce,
701
+ '1'
702
+ )
703
+ }
704
+ }
705
+ // Convert nonce to hex for tsParams
706
+ const nonceHex = toHex(nonce)
707
+
708
+ // Data:
709
+
710
+ let data
711
+ if (otherParams.data != null) {
712
+ data = otherParams.data
713
+ } else if (
714
+ edgeTransaction.currencyCode === this.currencyInfo.currencyCode
715
+ ) {
716
+ data = ''
717
+ } else {
718
+ const dataArray = abi.simpleEncode(
719
+ 'transfer(address,uint256):(uint256)',
720
+ otherParams.tokenRecipientAddress,
721
+ nativeAmountHex
722
+ )
723
+ data = '0x' + Buffer.from(dataArray).toString('hex')
724
+ nativeAmountHex = '0x00'
725
+ }
726
+
727
+ // Select the chain
728
+ const otherSettings =
729
+ this.currencyInfo.defaultSettings.otherSettings
730
+ const { chainParams } = otherSettings
731
+ const common = Common.custom(chainParams)
732
+
733
+ // Transaction Parameters
734
+ const txParams = {
735
+ nonce: nonceHex,
736
+ gasPrice: gasPriceHex,
737
+ gasLimit: gasLimitHex,
738
+ to: otherParams.to[0],
739
+ value: nativeAmountHex,
740
+ data
741
+ }
742
+
743
+ const privKey = Buffer.from(
744
+ this.walletInfo.keys[`${this.currencyInfo.pluginId}Key`],
745
+ 'hex'
746
+ )
747
+
748
+ // Log the private key address
749
+ const wallet = ethWallet.fromPrivateKey(privKey)
750
+ this.log.warn(`signTx getAddressString ${wallet.getAddressString()}`)
751
+
752
+ // Create and sign transaction
753
+ const unsignedTx = Transaction.fromTxData(txParams, { common })
754
+ const signedTx = unsignedTx.sign(privKey)
755
+
756
+ edgeTransaction.signedTx = bufToHex(signedTx.serialize())
757
+ edgeTransaction.txid = bufToHex(signedTx.hash())
758
+ edgeTransaction.date = Date.now() / 1000
759
+ if (edgeTransaction.otherParams) {
760
+ edgeTransaction.otherParams.nonceUsed = nonce
761
+ }
762
+ this.log.warn(`signTx\n${cleanTxLogs(edgeTransaction)}`)
763
+ return edgeTransaction
764
+ }
765
+
766
+ async broadcastTx(
767
+ edgeTransaction
768
+ ) {
769
+ await this.ethNetwork.multicastServers('broadcastTx', edgeTransaction)
770
+
771
+ // Success
772
+ this.log.warn(`SUCCESS broadcastTx\n${cleanTxLogs(edgeTransaction)}`)
773
+
774
+ return edgeTransaction
775
+ }
776
+
777
+ getDisplayPrivateSeed() {
778
+ if (
779
+ this.walletInfo.keys &&
780
+ this.walletInfo.keys[`${this.currencyInfo.pluginId}Key`]
781
+ ) {
782
+ return this.walletInfo.keys[`${this.currencyInfo.pluginId}Key`]
783
+ }
784
+ return ''
785
+ }
786
+
787
+ getDisplayPublicSeed() {
788
+ if (this.walletInfo.keys && this.walletInfo.keys.publicKey) {
789
+ return this.walletInfo.keys.publicKey
790
+ }
791
+ return ''
792
+ }
793
+
794
+ // Overload saveTx to mutate replaced transactions by RBF
795
+ async saveTx(edgeTransaction) {
796
+ // We must check if this transaction replaces another transaction
797
+ if (edgeTransaction.otherParams && edgeTransaction.otherParams.rbfTxid) {
798
+ const { currencyCode } = edgeTransaction
799
+
800
+ // Get the replaced transaction using the rbfTxid
801
+ const txid = edgeTransaction.otherParams.rbfTxid
802
+ const idx = this.findTransaction(currencyCode, txid)
803
+ const replacedEdgeTransaction = this.transactionList[currencyCode][idx]
804
+
805
+ // Use the RBF metadata because metadata for replaced transaction is not
806
+ // present in edge-currency-accountbased state
807
+ const metadata = edgeTransaction.metadata
808
+
809
+ // Update the transaction's blockHeight to -1 (drops the transaction)
810
+ const updatedEdgeTransaction = {
811
+ ...replacedEdgeTransaction,
812
+ metadata,
813
+ blockHeight: -1
814
+ }
815
+
816
+ this.addTransaction(currencyCode, updatedEdgeTransaction)
817
+ }
818
+
819
+ super.saveTx(edgeTransaction)
820
+ }
821
+
822
+ async addCustomToken(obj) {
823
+ let contractAddress = obj.contractAddress.replace('0x', '').toLowerCase()
824
+ if (!isHex(contractAddress) || contractAddress.length !== 40) {
825
+ throw new Error('ErrorInvalidContractAddress')
826
+ }
827
+ contractAddress = '0x' + contractAddress
828
+ super.addCustomToken(obj, contractAddress)
829
+ }
830
+ }
831
+
832
+ export { CurrencyEngine }