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,1216 @@
1
+ //
2
+ /* eslint-disable camelcase */
3
+
4
+ import { bns } from 'biggystring'
5
+ import { asEither } from 'cleaners'
6
+ import {
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+ InsufficientFundsError,
15
+ NoAmountSpecifiedError
16
+ } from 'edge-core-js/types'
17
+ import { Api, JsonRpc, RpcError } from 'eosjs'
18
+ import { JsSignatureProvider } from 'eosjs/dist/eosjs-jssig'
19
+ import { convertLegacyPublicKeys } from 'eosjs/dist/eosjs-numeric'
20
+ import EosApi from 'eosjs-api'
21
+ import parse from 'url-parse'
22
+
23
+ import { CurrencyEngine } from '../common/engine.js'
24
+ import {
25
+ asyncWaterfall,
26
+ cleanTxLogs,
27
+ getDenomInfo,
28
+ getOtherParams,
29
+ pickRandom,
30
+ validateObject
31
+ } from '../common/utils.js'
32
+ import { checkAddress, EosPlugin } from './eosPlugin.js'
33
+ import {
34
+ asDfuseGetKeyAccountsResponse,
35
+ asDfuseGetTransactionsErrorResponse,
36
+ asDfuseGetTransactionsResponse,
37
+ asGetAccountActivationQuote,
38
+ asHyperionGetTransactionResponse,
39
+ asHyperionTransaction,
40
+ dfuseGetTransactionsQueryString,
41
+ EosTransactionSuperNodeSchema
42
+ } from './eosSchema.js'
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+ const ADDRESS_POLL_MILLISECONDS = 10000
51
+ const BLOCKCHAIN_POLL_MILLISECONDS = 15000
52
+ const TRANSACTION_POLL_MILLISECONDS = 10000
53
+ // const ADDRESS_QUERY_LOOKBACK_BLOCKS = 0
54
+ const CHECK_TXS_HYPERION = true
55
+ const CHECK_TXS_FULL_NODES = true
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+ const bogusAccounts = {
66
+ ramdeathtest: true,
67
+ krpj4avazggi: true,
68
+ fobleos13125: true
69
+ }
70
+ class CosignAuthorityProvider {
71
+
72
+ constructor(rpc) {
73
+ this.rpc = rpc
74
+ }
75
+
76
+ async getRequiredKeys(args) {
77
+ const { transaction } = args
78
+ // Iterate over the actions and authorizations
79
+ transaction.actions.forEach((action, ti) => {
80
+ action.authorization.forEach((auth, ai) => {
81
+ // If the authorization matches the expected cosigner
82
+ // then remove it from the transaction while checking
83
+ // for what public keys are required
84
+ if (auth.actor === 'greymassfuel' && auth.permission === 'cosign') {
85
+ delete transaction.actions[ti].authorization.splice(ai, 1)
86
+ }
87
+ })
88
+ })
89
+ // the rpc below should be an already configured JsonRPC client from eosjs
90
+ return convertLegacyPublicKeys(
91
+ (
92
+ await this.rpc.fetch('/v1/chain/get_required_keys', {
93
+ transaction,
94
+ available_keys: args.availableKeys
95
+ })
96
+ ).required_keys
97
+ )
98
+ }
99
+ }
100
+ export class EosEngine extends CurrencyEngine {
101
+ // TODO: Add currency specific params
102
+ // Store any per wallet specific data in the `currencyEngine` object. Add any params
103
+ // to the EosEngine class definition in eosEngine.js and initialize them in the
104
+ // constructor()
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+ constructor(
113
+ currencyPlugin,
114
+ walletInfo,
115
+ opts,
116
+ fetchCors,
117
+ eosJsConfig
118
+ ) {
119
+ super(currencyPlugin, walletInfo, opts)
120
+ this.fetchCors = fetchCors
121
+ this.eosJsConfig = eosJsConfig
122
+ this.eosPlugin = currencyPlugin
123
+ this.activatedAccountsCache = {}
124
+ this.otherMethods = {
125
+ getAccountActivationQuote: async (params) => {
126
+ const {
127
+ requestedAccountName,
128
+ currencyCode,
129
+ ownerPublicKey,
130
+ activePublicKey,
131
+ requestedAccountCurrencyCode
132
+ } = params
133
+ if (!currencyCode || !requestedAccountName) {
134
+ throw new Error('ErrorInvalidParams')
135
+ }
136
+ if (!ownerPublicKey && !activePublicKey) {
137
+ throw new Error('ErrorInvalidParams')
138
+ }
139
+ if (!checkAddress(requestedAccountName)) {
140
+ const e = new Error('ErrorInvalidAccountName')
141
+ e.name = 'ErrorInvalidAccountName'
142
+ throw e
143
+ }
144
+
145
+ const options = {
146
+ method: 'POST',
147
+ headers: {
148
+ Accept: 'application/json',
149
+ 'Content-Type': 'application/json'
150
+ },
151
+ body: JSON.stringify({
152
+ requestedAccountName,
153
+ currencyCode,
154
+ ownerPublicKey,
155
+ activePublicKey,
156
+ requestedAccountCurrencyCode // chain ie TLOS or EOS
157
+ })
158
+ }
159
+
160
+ try {
161
+ const out = await asyncWaterfall(
162
+ this.currencyInfo.defaultSettings.otherSettings.eosActivationServers.map(
163
+ server => async () => {
164
+ const uri = `${server}/api/v1/activateAccount`
165
+ const response = await fetchCors(uri, options)
166
+ return response.json()
167
+ }
168
+ ),
169
+ 15000
170
+ )
171
+ return asGetAccountActivationQuote(out)
172
+ } catch (e) {
173
+ this.log.error(`getAccountActivationQuoteError: ${e}`)
174
+ throw new Error(`getAccountActivationQuoteError`)
175
+ }
176
+ }
177
+ }
178
+ }
179
+
180
+ async loadEngine(
181
+ plugin,
182
+ walletInfo,
183
+ opts
184
+ ) {
185
+ await super.loadEngine(plugin, walletInfo, opts)
186
+ if (typeof this.walletInfo.keys.ownerPublicKey !== 'string') {
187
+ if (walletInfo.keys.ownerPublicKey) {
188
+ this.walletInfo.keys.ownerPublicKey = walletInfo.keys.ownerPublicKey
189
+ } else {
190
+ const pubKeys = await plugin.derivePublicKey(this.walletInfo)
191
+ this.walletInfo.keys.ownerPublicKey = pubKeys.ownerPublicKey
192
+ }
193
+ }
194
+ }
195
+
196
+ // Poll on the blockheight
197
+ async checkBlockchainInnerLoop() {
198
+ try {
199
+ const result = await this.multicastServers('getInfo', {})
200
+ const blockHeight = result.head_block_num
201
+ if (this.walletLocalData.blockHeight !== blockHeight) {
202
+ this.checkDroppedTransactionsThrottled()
203
+ this.walletLocalData.blockHeight = blockHeight
204
+ this.walletLocalDataDirty = true
205
+ this.currencyEngineCallbacks.onBlockHeightChanged(
206
+ this.walletLocalData.blockHeight
207
+ )
208
+ }
209
+ } catch (e) {
210
+ this.log.error(`Error fetching height: ${e}`)
211
+ }
212
+ }
213
+
214
+ processIncomingTransaction(action) {
215
+ const result = validateObject(action, EosTransactionSuperNodeSchema)
216
+ if (!result) {
217
+ this.log.error('Invalid supernode tx')
218
+ return 0
219
+ }
220
+
221
+ const { act, trx_id, block_num } = action
222
+ const block_time = action['@timestamp']
223
+
224
+ const { from, to, memo, symbol } = act.data
225
+ const exchangeAmount = act.data.amount.toString()
226
+ const currencyCode = symbol
227
+ const ourReceiveAddresses = []
228
+ const denom = getDenomInfo(this.currencyInfo, currencyCode, this.allTokens)
229
+ if (!denom) {
230
+ this.log.error(
231
+ `processIncomingTransaction Received unsupported currencyCode: ${currencyCode}`
232
+ )
233
+ return 0
234
+ }
235
+ let nativeAmount = bns.mul(exchangeAmount, denom.multiplier)
236
+ let name = ''
237
+ if (to === this.walletLocalData.otherData.accountName) {
238
+ name = from
239
+ ourReceiveAddresses.push(to)
240
+ if (from === this.walletLocalData.otherData.accountName) {
241
+ // This is a spend to self. Make amount 0
242
+ nativeAmount = '0'
243
+ }
244
+ } else {
245
+ name = to
246
+ nativeAmount = `-${nativeAmount}`
247
+ }
248
+
249
+ const edgeTransaction = {
250
+ txid: trx_id,
251
+ date: Date.parse(block_time) / 1000,
252
+ currencyCode,
253
+ blockHeight: block_num > 0 ? block_num : 0,
254
+ nativeAmount,
255
+ networkFee: '0',
256
+ parentNetworkFee: '0',
257
+ ourReceiveAddresses,
258
+ signedTx: '',
259
+ otherParams: {},
260
+ metadata: {
261
+ name,
262
+ notes: memo
263
+ }
264
+ }
265
+
266
+ this.addTransaction(currencyCode, edgeTransaction)
267
+ return edgeTransaction.blockHeight
268
+ }
269
+
270
+ processOutgoingTransaction(action) {
271
+ const ourReceiveAddresses = []
272
+ // Hyperion nodes return a UTC timestamp without the Z suffix. We need to add it to parse it accurately.
273
+ const timestamp =
274
+ action['@timestamp'].indexOf('Z') === -1
275
+ ? action['@timestamp'] + 'Z'
276
+ : action['@timestamp']
277
+ const date = Date.parse(timestamp) / 1000
278
+ const blockHeight = action.block_num > 0 ? action.block_num : 0
279
+ if (!action.block_num) {
280
+ this.log.error(
281
+ `Invalid ${this.currencyInfo.currencyCode} transaction data. No tx block_num`
282
+ )
283
+ return 0
284
+ }
285
+ const txid = action.trx_id
286
+
287
+ if (!action.act) {
288
+ this.log.error(
289
+ `Invalid ${this.currencyInfo.currencyCode} transaction data. No action.act`
290
+ )
291
+ return 0
292
+ }
293
+ const name = action.act.name
294
+ // this.log('------------------------------------------------')
295
+ // this.log(`Txid: ${txid}`)
296
+ // this.log(`Action type: ${name}`)
297
+ if (name === 'transfer') {
298
+ if (!action.act.data) {
299
+ this.log.error(
300
+ `Invalid ${this.currencyInfo.currencyCode} transaction data. No action.act.data`
301
+ )
302
+ return 0
303
+ }
304
+ const { from, to, memo, amount, symbol } = action.act.data
305
+ const exchangeAmount = amount.toString()
306
+ const currencyCode = symbol
307
+
308
+ const denom = getDenomInfo(
309
+ this.currencyInfo,
310
+ currencyCode,
311
+ this.allTokens
312
+ )
313
+ // if invalid currencyCode then don't count as valid transaction
314
+ if (!denom) {
315
+ this.log.error(
316
+ `processOutgoingTransaction Received unsupported currencyCode: ${currencyCode}`
317
+ )
318
+ return 0
319
+ }
320
+ let nativeAmount = bns.mul(exchangeAmount, denom.multiplier)
321
+ // if sending to one's self
322
+ if (to === this.walletLocalData.otherData.accountName) {
323
+ ourReceiveAddresses.push(to)
324
+ if (from === this.walletLocalData.otherData.accountName) {
325
+ // This is a spend to self. Make amount 0
326
+ nativeAmount = '0'
327
+ }
328
+ } else {
329
+ nativeAmount = `-${nativeAmount}`
330
+ }
331
+
332
+ const edgeTransaction = {
333
+ txid,
334
+ date,
335
+ currencyCode,
336
+ blockHeight,
337
+ nativeAmount,
338
+ networkFee: '0',
339
+ parentNetworkFee: '0',
340
+ ourReceiveAddresses,
341
+ signedTx: '',
342
+ metadata: {
343
+ notes: memo
344
+ },
345
+ otherParams: { fromAddress: from, toAddress: to }
346
+ }
347
+
348
+ this.addTransaction(currencyCode, edgeTransaction)
349
+ // this.log(`From: ${from}`)
350
+ // this.log(`To: ${to}`)
351
+ // this.log(`Memo: ${memo}`)
352
+ // this.log(`Amount: ${exchangeAmount}`)
353
+ // this.log(`currencyCode: ${currencyCode}`)
354
+ }
355
+ return blockHeight
356
+ }
357
+
358
+ async checkOutgoingTransactions(
359
+ acct,
360
+ currencyCode
361
+ ) {
362
+ if (!CHECK_TXS_FULL_NODES) throw new Error('Dont use full node API')
363
+ const limit = 10
364
+ let skip = 0
365
+ let finish = false
366
+
367
+ let newHighestTxHeight =
368
+ this.walletLocalData.otherData.lastQueryActionSeq[currencyCode] || 0
369
+
370
+ while (!finish) {
371
+ // query the server / node
372
+ const params = {
373
+ direction: 'outgoing',
374
+ acct,
375
+ currencyCode,
376
+ skip,
377
+ limit,
378
+ low: newHighestTxHeight + 1
379
+ }
380
+ const actionsObject = await this.multicastServers(
381
+ 'getOutgoingTransactions',
382
+ params
383
+ )
384
+ let actions = []
385
+ // if the actions array is not empty, then set the actions variable
386
+ if (actionsObject.actions && actionsObject.actions.length > 0) {
387
+ actions = actionsObject.actions
388
+ } else {
389
+ break
390
+ }
391
+ for (let i = 0; i < actions.length; i++) {
392
+ const action = actions[i]
393
+ const blockNum = this.processOutgoingTransaction(action)
394
+ // if the block height for the transaction is greater than the previously highest block height
395
+ if (blockNum > newHighestTxHeight) {
396
+ newHighestTxHeight = blockNum
397
+ } else if (blockNum === newHighestTxHeight && i === 0 && skip === 0) {
398
+ // If on the first query, we get blockHeights equal to the previously cached heights
399
+ // then stop query as we assume we're just getting back previously queried data
400
+ finish = true
401
+ break
402
+ }
403
+ }
404
+ // if there are no actions or it's less than the limit (we're at the end)
405
+ if (!actions.length || actions.length < limit) {
406
+ break
407
+ }
408
+ skip += 10
409
+ }
410
+ // if there have been new valid actions then increase the last sequence number
411
+ if (
412
+ newHighestTxHeight >
413
+ (this.walletLocalData.otherData.lastQueryActionSeq[currencyCode] || 0)
414
+ ) {
415
+ this.walletLocalData.otherData.lastQueryActionSeq[currencyCode] =
416
+ newHighestTxHeight
417
+ this.walletLocalDataDirty = true
418
+ }
419
+ return true
420
+ }
421
+
422
+ // similar to checkOutgoingTransactions, possible to refactor
423
+ async checkIncomingTransactions(
424
+ acct,
425
+ currencyCode
426
+ ) {
427
+ if (!CHECK_TXS_HYPERION) throw new Error('Dont use Hyperion API')
428
+
429
+ let newHighestTxHeight =
430
+ this.walletLocalData.otherData.highestTxHeight[currencyCode] || 0
431
+
432
+ const limit = 10
433
+ let skip = 0
434
+ let finish = false
435
+
436
+ while (!finish) {
437
+ this.log(
438
+ 'looping through checkIncomingTransactions, newHighestTxHeight: ',
439
+ newHighestTxHeight
440
+ )
441
+ // Use hyperion API with a block producer. "transfers" essentially mean transactions
442
+ // may want to move to get_actions at the request of block producer
443
+ const params = {
444
+ direction: 'incoming',
445
+ acct,
446
+ currencyCode,
447
+ skip,
448
+ limit,
449
+ low: newHighestTxHeight + 1
450
+ }
451
+ const actionsObject = await this.multicastServers(
452
+ 'getIncomingTransactions',
453
+ params
454
+ )
455
+ let actions = []
456
+ // sort transactions by block height (blockNum) since they can be out of order
457
+ actionsObject.actions.sort((a, b) => b.block_num - a.block_num)
458
+
459
+ // if there are no actions
460
+ if (actionsObject.actions && actionsObject.actions.length > 0) {
461
+ actions = actionsObject.actions
462
+ } else {
463
+ break
464
+ }
465
+
466
+ for (let i = 0; i < actions.length; i++) {
467
+ const action = actions[i]
468
+ const blockNum = this.processIncomingTransaction(action)
469
+ // if the block height for the transaction is greater than the previously highest block height
470
+ // then set new highest block height
471
+ if (blockNum > newHighestTxHeight) {
472
+ newHighestTxHeight = blockNum
473
+ } else if (blockNum === newHighestTxHeight && i === 0 && skip === 0) {
474
+ // If on the first query, we get blockHeights equal to the previously cached heights
475
+ // then stop query as we assume we're just getting back previously queried data
476
+ finish = true
477
+ break
478
+ }
479
+ }
480
+ if (!actions.length || actions.length < limit) {
481
+ break
482
+ }
483
+ skip += 10
484
+ }
485
+ if (
486
+ newHighestTxHeight >
487
+ (this.walletLocalData.otherData.highestTxHeight[currencyCode] || 0)
488
+ ) {
489
+ this.walletLocalData.otherData.highestTxHeight[currencyCode] =
490
+ newHighestTxHeight
491
+ this.walletLocalDataDirty = true
492
+ }
493
+ return true
494
+ }
495
+
496
+ async checkTransactionsInnerLoop() {
497
+ const { enabledTokens } = this.walletLocalData
498
+ if (
499
+ !this.walletLocalData.otherData ||
500
+ !this.walletLocalData.otherData.accountName
501
+ ) {
502
+ return
503
+ }
504
+ const acct = this.walletLocalData.otherData.accountName
505
+
506
+ for (const token of enabledTokens) {
507
+ let incomingResult, outgoingResult
508
+ try {
509
+ incomingResult = await this.checkIncomingTransactions(acct, token)
510
+ outgoingResult = await this.checkOutgoingTransactions(acct, token)
511
+ } catch (e) {
512
+ this.log.error(
513
+ `checkTransactionsInnerLoop fetches failed with error: ${e.name} ${e.message}`
514
+ )
515
+ return false
516
+ }
517
+
518
+ if (incomingResult && outgoingResult) {
519
+ this.tokenCheckTransactionsStatus[token] = 1
520
+ this.updateOnAddressesChecked()
521
+ }
522
+ if (this.transactionsChangedArray.length > 0) {
523
+ this.currencyEngineCallbacks.onTransactionsChanged(
524
+ this.transactionsChangedArray
525
+ )
526
+ this.transactionsChangedArray = []
527
+ }
528
+ }
529
+ }
530
+
531
+ async multicastServers(func, ...params) {
532
+ const { currencyCode } = this.currencyInfo
533
+ let out = { result: '', server: 'no server' }
534
+ switch (func) {
535
+ case 'getIncomingTransactions':
536
+ case 'getOutgoingTransactions': {
537
+ const { direction, acct, currencyCode, skip, limit, low } = params[0]
538
+ const hyperionFuncs =
539
+ this.currencyInfo.defaultSettings.otherSettings.eosHyperionNodes.map(
540
+ server => async () => {
541
+ const url =
542
+ server +
543
+ `/v2/history/get_actions?transfer.${
544
+ direction === 'outgoing' ? 'from' : 'to'
545
+ }=${acct}&transfer.symbol=${currencyCode}&skip=${skip}&limit=${limit}&sort=desc`
546
+ const response = await this.eosJsConfig.fetch(url)
547
+ const parsedUrl = parse(url, {}, true)
548
+ if (!response.ok) {
549
+ this.log.error('multicast in / out tx server error: ', server)
550
+ throw new Error(
551
+ `The server returned error code ${response.status} for ${parsedUrl.hostname}`
552
+ )
553
+ }
554
+ const result = asHyperionGetTransactionResponse(
555
+ await response.json()
556
+ )
557
+ return { server, result }
558
+ }
559
+ )
560
+ const dfuseFuncs =
561
+ this.currencyInfo.defaultSettings.otherSettings.eosDfuseServers.map(
562
+ server => async () => {
563
+ if (this.currencyInfo.currencyCode !== 'EOS')
564
+ throw new Error('dfuse only supports EOS')
565
+ const response = await this.eosJsConfig.fetch(
566
+ `${server}/graphql`,
567
+ {
568
+ method: 'POST',
569
+ headers: {
570
+ 'Content-Type': 'application/json'
571
+ },
572
+ body: JSON.stringify({
573
+ query: dfuseGetTransactionsQueryString,
574
+ variables: {
575
+ query: `${
576
+ direction === 'outgoing' ? 'auth' : 'receiver'
577
+ }:${acct} action:transfer`,
578
+ limit,
579
+ low
580
+ }
581
+ })
582
+ }
583
+ )
584
+ const responseJson = asEither(
585
+ asDfuseGetTransactionsResponse,
586
+ asDfuseGetTransactionsErrorResponse
587
+ )(await response.json())
588
+ if (responseJson.errors != null) {
589
+ this.log.warn(
590
+ `dfuse ${server} get transactions failed: ${JSON.stringify(
591
+ responseJson.errors[0]
592
+ )}`
593
+ )
594
+ throw new Error(responseJson.errors[0].message)
595
+ }
596
+ // Convert txs to Hyperion
597
+ const actions =
598
+ responseJson.data.searchTransactionsBackward.results.map(tx =>
599
+ asHyperionTransaction({
600
+ trx_id: tx.trace.id,
601
+ '@timestamp': tx.trace.block.timestamp,
602
+ block_num: tx.trace.block.num,
603
+ act: {
604
+ authorization:
605
+ tx.trace.matchingActions[0].authorization[0],
606
+ data: {
607
+ from: tx.trace.matchingActions[0].json.from,
608
+ to: tx.trace.matchingActions[0].json.to,
609
+ // quantity: "0.0001 EOS"
610
+ amount: Number(
611
+ tx.trace.matchingActions[0].json.quantity.split(
612
+ ' '
613
+ )[0]
614
+ ),
615
+ symbol:
616
+ tx.trace.matchingActions[0].json.quantity.split(
617
+ ' '
618
+ )[1],
619
+ memo: tx.trace.matchingActions[0].json.memo
620
+ }
621
+ }
622
+ })
623
+ )
624
+ return { server, result: { actions } }
625
+ }
626
+ )
627
+ out = await asyncWaterfall([...hyperionFuncs, ...dfuseFuncs])
628
+ break
629
+ }
630
+
631
+ case 'getKeyAccounts': {
632
+ const body = JSON.stringify({
633
+ public_key: params[0]
634
+ })
635
+ const hyperionFuncs =
636
+ this.currencyInfo.defaultSettings.otherSettings.eosHyperionNodes.map(
637
+ server => async () => {
638
+ const authorizersReply = await this.eosJsConfig.fetch(
639
+ `${server}/v1/history/get_key_accounts`,
640
+ {
641
+ method: 'POST',
642
+ body,
643
+ headers: {
644
+ 'Content-Type': 'application/json'
645
+ }
646
+ }
647
+ )
648
+ if (!authorizersReply.ok) {
649
+ throw new Error(
650
+ `${server} get_key_accounts failed with ${JSON.stringify(
651
+ authorizersReply
652
+ )}`
653
+ )
654
+ }
655
+ const authorizersData = await authorizersReply.json()
656
+ // verify array order (chronological)?
657
+ if (!authorizersData.account_names[0]) {
658
+ // indicates no activation has occurred
659
+ // set flag to indicate whether has hit activation API
660
+ // only do once per login (makeEngine)
661
+ if (
662
+ this.currencyInfo.defaultSettings.otherSettings
663
+ .createAccountViaSingleApiEndpoints &&
664
+ this.currencyInfo.defaultSettings.otherSettings
665
+ .createAccountViaSingleApiEndpoints.length > 0
666
+ ) {
667
+ const { publicKey, ownerPublicKey } = this.walletInfo.keys
668
+
669
+ const { createAccountViaSingleApiEndpoints } =
670
+ this.currencyInfo.defaultSettings.otherSettings
671
+ const request = await this.fetchCors(
672
+ createAccountViaSingleApiEndpoints[0],
673
+ {
674
+ method: 'POST',
675
+ body: JSON.stringify({
676
+ ownerPublicKey,
677
+ activePublicKey: publicKey
678
+ }),
679
+ headers: {
680
+ Accept: 'application/json',
681
+ 'Content-Type': 'application/json'
682
+ }
683
+ }
684
+ )
685
+ const response = await request.json()
686
+ const { accountName, transactionId } = response
687
+ if (!accountName) throw new Error(response)
688
+ this.log.warn(
689
+ `Account created with accountName: ${accountName} and transactionId: ${transactionId}`
690
+ )
691
+ }
692
+ throw new Error(
693
+ `${server} could not find account with public key: ${params[0]}`
694
+ )
695
+ }
696
+ const accountName = authorizersData.account_names[0]
697
+ const getAccountBody = JSON.stringify({
698
+ account_name: accountName
699
+ })
700
+ const accountReply = await this.eosJsConfig.fetch(
701
+ `${server}/v1/chain/get_account`,
702
+ {
703
+ method: 'POST',
704
+ body: getAccountBody
705
+ }
706
+ )
707
+ if (!accountReply.ok) {
708
+ throw new Error(
709
+ `${server} get_account failed with ${authorizersReply}`
710
+ )
711
+ }
712
+ return { server, result: await accountReply.json() }
713
+ }
714
+ )
715
+ // dfuse API is EOS only
716
+ const dfuseFuncs =
717
+ this.currencyInfo.defaultSettings.otherSettings.eosDfuseServers.map(
718
+ server => async () => {
719
+ if (this.currencyInfo.currencyCode !== 'EOS')
720
+ throw new Error('dfuse only supports EOS')
721
+ const response = await this.eosJsConfig.fetch(
722
+ `${server}/v0/state/key_accounts?public_key=${params[0]}`
723
+ )
724
+ if (!response.ok) {
725
+ throw new Error(
726
+ `${server} get_account failed with ${response.code}`
727
+ )
728
+ }
729
+ const responseJson = asDfuseGetKeyAccountsResponse(
730
+ await response.json()
731
+ )
732
+ if (responseJson.account_names.length === 0)
733
+ throw new Error('dfuse returned empty array')
734
+ return {
735
+ server,
736
+ result: {
737
+ account_name: responseJson.account_names[0]
738
+ }
739
+ }
740
+ }
741
+ )
742
+ out = await asyncWaterfall([...hyperionFuncs, ...dfuseFuncs])
743
+ break
744
+ }
745
+
746
+ case 'getCurrencyBalance':
747
+ case 'getInfo': {
748
+ const { eosNodes } = this.currencyInfo.defaultSettings.otherSettings
749
+ const randomNodes = pickRandom(eosNodes, 3)
750
+ out = await asyncWaterfall(
751
+ randomNodes.map(server => async () => {
752
+ const eosServer = EosApi({
753
+ ...this.eosJsConfig,
754
+ httpEndpoint: server
755
+ })
756
+ const result = await eosServer[func](...params)
757
+ return { server, result }
758
+ })
759
+ )
760
+ break
761
+ }
762
+ case 'transact': {
763
+ const { eosFuelServers, eosNodes } =
764
+ this.currencyInfo.defaultSettings.otherSettings
765
+ const randomNodes =
766
+ eosFuelServers.length > 0
767
+ ? pickRandom(eosFuelServers, 30)
768
+ : pickRandom(eosNodes, 30)
769
+ out = await asyncWaterfall(
770
+ randomNodes.map(server => async () => {
771
+ const rpc = new JsonRpc(server, {
772
+ fetch: (...args) => {
773
+ // this.log(`LoggedFetch: ${JSON.stringify(args)}`)
774
+ return this.eosJsConfig.fetch(...args)
775
+ }
776
+ })
777
+ const keys = params[1].keyProvider ? params[1].keyProvider : []
778
+ params[1] = {
779
+ ...params[1],
780
+ blocksBehind: 3,
781
+ expireSeconds: 30
782
+ }
783
+ const signatureProvider = new JsSignatureProvider(keys)
784
+ const eos = new Api({
785
+ // Pass in new authorityProvider
786
+ authorityProvider: new CosignAuthorityProvider(rpc),
787
+ rpc,
788
+ signatureProvider,
789
+ textDecoder: new TextDecoder(),
790
+ textEncoder: new TextEncoder()
791
+ })
792
+ const result = await eos[func](...params)
793
+ return { server, result }
794
+ })
795
+ )
796
+
797
+ break
798
+ }
799
+ }
800
+
801
+ this.log(`${currencyCode} multicastServers ${func} ${out.server} won`)
802
+ return out.result
803
+ }
804
+
805
+ // Check all account balance and other relevant info
806
+ async checkAccountInnerLoop() {
807
+ const publicKey = this.walletLocalData.publicKey
808
+ try {
809
+ if (bogusAccounts[this.walletLocalData.otherData.accountName]) {
810
+ this.walletLocalData.otherData.accountName = ''
811
+ this.walletLocalDataDirty = true
812
+ this.currencyEngineCallbacks.onAddressChanged()
813
+ }
814
+ // Check if the publicKey has an account accountName
815
+ if (!this.walletLocalData.otherData.accountName) {
816
+ const account = await this.multicastServers('getKeyAccounts', publicKey)
817
+ if (account && !bogusAccounts[account.account_name]) {
818
+ this.walletLocalData.otherData.accountName = account.account_name
819
+ this.walletLocalDataDirty = true
820
+ this.currencyEngineCallbacks.onAddressChanged()
821
+ }
822
+ }
823
+
824
+ // Check balance on account
825
+ if (this.walletLocalData.otherData.accountName) {
826
+ for (const token of this.allTokens) {
827
+ if (this.walletLocalData.enabledTokens.includes(token.currencyCode)) {
828
+ const results = await this.multicastServers(
829
+ 'getCurrencyBalance',
830
+ token.contractAddress,
831
+ this.walletLocalData.otherData.accountName
832
+ )
833
+ if (results && results.length > 0) {
834
+ for (const r of results) {
835
+ if (typeof r === 'string') {
836
+ const balanceArray = r.split(' ')
837
+ if (balanceArray.length === 2) {
838
+ const exchangeAmount = balanceArray[0]
839
+ const currencyCode = balanceArray[1]
840
+ let nativeAmount = ''
841
+
842
+ // Convert exchange amount to native amount
843
+ const denom = getDenomInfo(
844
+ this.currencyInfo,
845
+ currencyCode,
846
+ [...this.customTokens, ...this.allTokens]
847
+ )
848
+ if (denom && denom.multiplier) {
849
+ nativeAmount = bns.mul(exchangeAmount, denom.multiplier)
850
+ } else {
851
+ this.log(
852
+ `Received balance for unsupported currencyCode: ${currencyCode}`
853
+ )
854
+ }
855
+
856
+ if (!this.walletLocalData.totalBalances[currencyCode]) {
857
+ this.walletLocalData.totalBalances[currencyCode] = '0'
858
+ }
859
+ if (
860
+ !bns.eq(
861
+ this.walletLocalData.totalBalances[currencyCode],
862
+ nativeAmount
863
+ )
864
+ ) {
865
+ this.walletLocalData.totalBalances[currencyCode] =
866
+ nativeAmount
867
+ this.walletLocalDataDirty = true
868
+ this.currencyEngineCallbacks.onBalanceChanged(
869
+ currencyCode,
870
+ nativeAmount
871
+ )
872
+ this.log.warn(
873
+ `Updated ${currencyCode} balance ${nativeAmount}`
874
+ )
875
+ }
876
+ }
877
+ }
878
+ }
879
+ }
880
+ this.tokenCheckBalanceStatus[token.currencyCode] = 1
881
+ }
882
+ }
883
+ }
884
+ this.updateOnAddressesChecked()
885
+ } catch (e) {
886
+ this.log.error(`Error fetching account: ${e}`)
887
+ }
888
+ }
889
+
890
+ async clearBlockchainCache() {
891
+ this.activatedAccountsCache = {}
892
+ await super.clearBlockchainCache()
893
+ this.walletLocalData.otherData.lastQueryActionSeq = {}
894
+ this.walletLocalData.otherData.highestTxHeight = {}
895
+ this.walletLocalData.otherData.accountName = ''
896
+ }
897
+
898
+ // ****************************************************************************
899
+ // Public methods
900
+ // ****************************************************************************
901
+
902
+ // This routine is called once a wallet needs to start querying the network
903
+ async startEngine() {
904
+ this.engineOn = true
905
+
906
+ this.addToLoop('checkBlockchainInnerLoop', BLOCKCHAIN_POLL_MILLISECONDS)
907
+ this.addToLoop('checkAccountInnerLoop', ADDRESS_POLL_MILLISECONDS)
908
+ this.addToLoop('checkTransactionsInnerLoop', TRANSACTION_POLL_MILLISECONDS)
909
+ super.startEngine()
910
+ }
911
+
912
+ async resyncBlockchain() {
913
+ await this.killEngine()
914
+ await this.clearBlockchainCache()
915
+ await this.startEngine()
916
+ }
917
+
918
+ getFreshAddress(options) {
919
+ if (this.walletLocalData.otherData.accountName) {
920
+ return { publicAddress: this.walletLocalData.otherData.accountName }
921
+ } else {
922
+ // Account is not yet active. Return the publicKeys so the user can activate the account
923
+ return {
924
+ publicAddress: '',
925
+ publicKey: this.walletInfo.keys.publicKey,
926
+ ownerPublicKey: this.walletInfo.keys.ownerPublicKey
927
+ }
928
+ }
929
+ }
930
+
931
+ async makeSpend(edgeSpendInfoIn) {
932
+ const { edgeSpendInfo, currencyCode, nativeBalance, denom } =
933
+ super.makeSpend(edgeSpendInfoIn)
934
+ const { defaultSettings } = this.currencyInfo
935
+ const tokenInfo = this.getTokenInfo(currencyCode)
936
+ if (!tokenInfo) throw new Error('Unable to find token info')
937
+ const { contractAddress } = tokenInfo
938
+ const nativeDenomination = getDenomInfo(
939
+ this.currencyInfo,
940
+ currencyCode,
941
+ this.allTokens
942
+ )
943
+ if (!nativeDenomination) {
944
+ throw new Error(`Error: no native denomination found for ${currencyCode}`)
945
+ }
946
+ const nativePrecision = nativeDenomination.multiplier.length - 1
947
+ if (edgeSpendInfo.spendTargets.length !== 1) {
948
+ throw new Error('Error: only one output allowed')
949
+ }
950
+ const publicAddress = edgeSpendInfo.spendTargets[0].publicAddress
951
+
952
+ // Check if destination address is activated
953
+ let mustCreateAccount = false
954
+ const activated = this.activatedAccountsCache[publicAddress]
955
+ if (activated !== undefined && activated === false) {
956
+ mustCreateAccount = true
957
+ } else if (activated === undefined) {
958
+ try {
959
+ await this.eosPlugin.getAccSystemStats(publicAddress)
960
+ this.activatedAccountsCache[publicAddress] = true
961
+ } catch (e) {
962
+ if (e.code.includes('ErrorUnknownAccount')) {
963
+ this.activatedAccountsCache[publicAddress] = false
964
+ mustCreateAccount = true
965
+ } else {
966
+ this.log.error(`makeSpend eosPlugin.getAccSystemStats Error ${e}`)
967
+ throw e
968
+ }
969
+ }
970
+ }
971
+ if (mustCreateAccount) {
972
+ throw new Error('ErrorAccountNotActivated')
973
+ }
974
+
975
+ let nativeAmount = '0'
976
+ if (typeof edgeSpendInfo.spendTargets[0].nativeAmount === 'string') {
977
+ nativeAmount = edgeSpendInfo.spendTargets[0].nativeAmount
978
+ } else {
979
+ throw new NoAmountSpecifiedError()
980
+ }
981
+
982
+ if (bns.eq(nativeAmount, '0')) {
983
+ throw new NoAmountSpecifiedError()
984
+ }
985
+ const exchangeAmount = bns.div(
986
+ nativeAmount,
987
+ denom.multiplier,
988
+ nativePrecision
989
+ )
990
+ const networkFee = '0'
991
+ if (bns.gt(nativeAmount, nativeBalance)) {
992
+ throw new InsufficientFundsError()
993
+ }
994
+
995
+ const quantity =
996
+ bns.toFixed(exchangeAmount, nativePrecision) + ` ${currencyCode}`
997
+ let memo = ''
998
+ if (
999
+ edgeSpendInfo.spendTargets[0].otherParams &&
1000
+ typeof edgeSpendInfo.spendTargets[0].otherParams.uniqueIdentifier ===
1001
+ 'string'
1002
+ ) {
1003
+ memo = edgeSpendInfo.spendTargets[0].otherParams.uniqueIdentifier
1004
+ }
1005
+
1006
+ const transferActions = [
1007
+ {
1008
+ account: contractAddress,
1009
+ name: 'transfer',
1010
+ authorization: [
1011
+ {
1012
+ actor: this.walletLocalData.otherData.accountName,
1013
+ permission: 'active'
1014
+ }
1015
+ ],
1016
+ data: {
1017
+ from: this.walletLocalData.otherData.accountName,
1018
+ to: publicAddress,
1019
+ quantity,
1020
+ memo
1021
+ }
1022
+ }
1023
+ ]
1024
+ const { fuelActions = [] } = defaultSettings.otherSettings
1025
+ const transactionJson = {
1026
+ actions: [...fuelActions, ...transferActions]
1027
+ }
1028
+ // XXX Greymass doesn't let us hit their servers too often
1029
+ // Create an unsigned transaction to catch any errors
1030
+ // await this.multicastServers('transact', transactionJson, {
1031
+ // sign: false,
1032
+ // broadcast: false
1033
+ // })
1034
+
1035
+ nativeAmount = `-${nativeAmount}`
1036
+
1037
+ // const idInternal = Buffer.from(this.io.random(32)).toString('hex')
1038
+ const edgeTransaction = {
1039
+ txid: '', // txid
1040
+ date: 0, // date
1041
+ currencyCode, // currencyCode
1042
+ blockHeight: 0, // blockHeight
1043
+ nativeAmount, // nativeAmount
1044
+ networkFee, // networkFee
1045
+ ourReceiveAddresses: [], // ourReceiveAddresses
1046
+ signedTx: '', // signedTx
1047
+ otherParams: {
1048
+ transactionJson
1049
+ }
1050
+ }
1051
+ this.log.warn(
1052
+ `${this.currencyInfo.currencyCode} tx prepared: ${nativeAmount} ${this.walletLocalData.publicKey} -> ${publicAddress}`
1053
+ )
1054
+ return edgeTransaction
1055
+ }
1056
+
1057
+ // async makeSpend (edgeSpendInfo: EdgeSpendInfo) {
1058
+ // // // Validate the spendInfo
1059
+ // const valid = validateObject(edgeSpendInfo, MakeSpendSchema)
1060
+
1061
+ // if (!valid) {
1062
+ // throw (new Error('Error: invalid EdgeSpendInfo'))
1063
+ // }
1064
+
1065
+ // // TODO: Validate the number of destination targets supported by this currency.
1066
+ // // ie. Bitcoin can do multiple targets. Ethereum only one
1067
+ // // edgeSpendInfo.spendTargets.length
1068
+
1069
+ // // TODO: Validate for valid currencyCode which will be in
1070
+ // // edgeSpendInfo.currencyCode if specified by user. Otherwise use native currency
1071
+
1072
+ // // TODO: Get nativeAmount which is denoted is small currency unit. ie satoshi/wei
1073
+ // // edgeSpendInfo.spendTargets[0].nativeAmount
1074
+ // //
1075
+ // // Throw if this currency cannot spend a 0 amount
1076
+ // // if (bns.eq(nativeAmount, '0')) {
1077
+ // // throw (new error.NoAmountSpecifiedError())
1078
+ // // }
1079
+
1080
+ // // TODO: Get current wallet balance and make sure there are sufficient funds including fees
1081
+ // // const nativeBalance = this.walletLocalData.totalBalances[currencyCode]
1082
+
1083
+ // // TODO: Extract unique identifier for this transaction. This is known as a Payment ID for
1084
+ // // Monero, Destination Tag for Ripple, and Memo ID for Stellar. Use if currency is capable
1085
+ // // edgeSpendInfo.spendTargets[0].otherParams.uniqueIdentifier
1086
+
1087
+ // // TODO: Create an EdgeTransaction object with the following params filled out:
1088
+ // // currencyCode
1089
+ // // blockHeight = 0
1090
+ // // nativeAmount (which includes fee)
1091
+ // // networkFee (in smallest unit of currency)
1092
+ // // ourReceiveAddresses = []
1093
+ // // signedTx = ''
1094
+ // // otherParams. Object declared in this currency's types.js file (ie. eosTypes.js)
1095
+ // // which are additional params useful for signing and broadcasting transaction
1096
+ // const edgeTransaction: EdgeTransaction = {
1097
+ // txid: '', // txid
1098
+ // date: 0, // date
1099
+ // currencyCode: '', // currencyCode
1100
+ // blockHeight: 0, // blockHeight
1101
+ // nativeAmount: '', // nativeAmount
1102
+ // networkFee: '', // networkFee
1103
+ // ourReceiveAddresses: [], // ourReceiveAddresses
1104
+ // signedTx: '', // signedTx
1105
+ // otherParams: {}
1106
+ // }
1107
+
1108
+ // this.log('Payment transaction prepared...')
1109
+ // return edgeTransaction
1110
+ // }
1111
+
1112
+ async signTx(edgeTransaction) {
1113
+ // const otherParams = getOtherParams(edgeTransaction)
1114
+
1115
+ // Do signing
1116
+ // Take the private key from this.walletInfo.keys.eosKey and sign the transaction
1117
+ // const privateKey = this.walletInfo.keys.eosKey
1118
+ // const keyProvider = []
1119
+ // if (this.walletInfo.keys.eosKey) {
1120
+ // keyProvider.push(this.walletInfo.keys.eosKey)
1121
+ // }
1122
+ // if (this.walletInfo.keys.eosOwnerKey) {
1123
+ // keyProvider.push(this.walletInfo.keys.eosOwnerKey)
1124
+ // }
1125
+ // XXX Greymass doesn't let us hit their servers too often
1126
+ // await this.multicastServers('transact', otherParams.transactionJson, {
1127
+ // keyProvider,
1128
+ // sign: true,
1129
+ // broadcast: false
1130
+ // })
1131
+
1132
+ // Complete edgeTransaction.txid params if possible at this state
1133
+ return edgeTransaction
1134
+ }
1135
+
1136
+ async broadcastTx(
1137
+ edgeTransaction
1138
+ ) {
1139
+ const otherParams = getOtherParams(edgeTransaction)
1140
+ // Broadcast transaction and add date
1141
+ const keyProvider = []
1142
+ if (this.walletInfo.keys.eosKey) {
1143
+ keyProvider.push(this.walletInfo.keys.eosKey)
1144
+ }
1145
+ // usage of eosOwnerKey must be protected by conditional
1146
+ // checking for its existence
1147
+ if (this.walletInfo.keys.eosOwnerKey) {
1148
+ keyProvider.push(this.walletInfo.keys.eosOwnerKey)
1149
+ }
1150
+ try {
1151
+ const signedTx = await this.multicastServers(
1152
+ 'transact',
1153
+ otherParams.transactionJson,
1154
+ {
1155
+ keyProvider,
1156
+ sign: true,
1157
+ broadcast: true
1158
+ }
1159
+ )
1160
+ edgeTransaction.date = Date.now() / 1000
1161
+ edgeTransaction.txid = signedTx.transaction_id
1162
+ this.log.warn(`SUCCESS broadcastTx\n${cleanTxLogs(edgeTransaction)}`)
1163
+ return edgeTransaction
1164
+ } catch (e) {
1165
+ this.log.error('\nCaught exception: ' + e)
1166
+ if (e instanceof RpcError) this.log.error(JSON.stringify(e.json, null, 2))
1167
+ let err = e
1168
+ if (err.error) {
1169
+ this.log.error(`err.error= ${err.error}`)
1170
+ this.log.error(`err.error.name= ${err.error.name}`)
1171
+ }
1172
+ try {
1173
+ err = JSON.parse(e)
1174
+ } catch (e2) {
1175
+ throw e
1176
+ }
1177
+ if (err.error && err.error.name === 'tx_net_usage_exceeded') {
1178
+ err = new Error('Insufficient NET available to send EOS transaction')
1179
+ err.name = 'ErrorEosInsufficientNet'
1180
+ } else if (err.error && err.error.name === 'tx_cpu_usage_exceeded') {
1181
+ err = new Error('Insufficient CPU available to send EOS transaction')
1182
+ err.name = 'ErrorEosInsufficientCpu'
1183
+ } else if (err.error && err.error.name === 'ram_usage_exceeded') {
1184
+ err = new Error('Insufficient RAM available to send EOS transaction')
1185
+ err.name = 'ErrorEosInsufficientRam'
1186
+ }
1187
+ throw err
1188
+ }
1189
+ }
1190
+
1191
+ getDisplayPrivateSeed() {
1192
+ let out = ''
1193
+ // usage of eosOwnerKey must be protected by conditional
1194
+ // checking for its existence
1195
+ if (this.walletInfo.keys && this.walletInfo.keys.eosOwnerKey) {
1196
+ out += 'owner key\n' + this.walletInfo.keys.eosOwnerKey + '\n\n'
1197
+ }
1198
+ if (this.walletInfo.keys && this.walletInfo.keys.eosKey) {
1199
+ out += 'active key\n' + this.walletInfo.keys.eosKey + '\n\n'
1200
+ }
1201
+ return out
1202
+ }
1203
+
1204
+ getDisplayPublicSeed() {
1205
+ let out = ''
1206
+ if (this.walletInfo.keys && this.walletInfo.keys.ownerPublicKey) {
1207
+ out += 'owner publicKey\n' + this.walletInfo.keys.ownerPublicKey + '\n\n'
1208
+ }
1209
+ if (this.walletInfo.keys && this.walletInfo.keys.publicKey) {
1210
+ out += 'active publicKey\n' + this.walletInfo.keys.publicKey + '\n\n'
1211
+ }
1212
+ return out
1213
+ }
1214
+ }
1215
+
1216
+ export { CurrencyEngine }