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.
- package/CHANGELOG.md +713 -0
- package/LICENSE +29 -0
- package/README.md +63 -0
- package/index.js +3 -0
- package/lib/binance/bnbEngine.js +591 -0
- package/lib/binance/bnbInfo.js +43 -0
- package/lib/binance/bnbPlugin.js +168 -0
- package/lib/binance/bnbSchema.js +83 -0
- package/lib/binance/bnbTypes.js +39 -0
- package/lib/common/engine.js +918 -0
- package/lib/common/plugin.js +152 -0
- package/lib/common/schema.js +108 -0
- package/lib/common/types.js +85 -0
- package/lib/common/utils.js +378 -0
- package/lib/eos/eosEngine.js +1216 -0
- package/lib/eos/eosInfo.js +98 -0
- package/lib/eos/eosPlugin.js +314 -0
- package/lib/eos/eosSchema.js +190 -0
- package/lib/eos/eosTypes.js +88 -0
- package/lib/eos/telosInfo.js +94 -0
- package/lib/eos/waxInfo.js +95 -0
- package/lib/ethereum/etcInfo.js +121 -0
- package/lib/ethereum/ethEngine.js +832 -0
- package/lib/ethereum/ethInfo.js +1300 -0
- package/lib/ethereum/ethMiningFees.js +157 -0
- package/lib/ethereum/ethNetwork.js +2195 -0
- package/lib/ethereum/ethPlugin.js +377 -0
- package/lib/ethereum/ethSchema.js +61 -0
- package/lib/ethereum/ethTypes.js +461 -0
- package/lib/ethereum/ftminfo.js +102 -0
- package/lib/ethereum/rskInfo.js +101 -0
- package/lib/fio/fioConst.js +38 -0
- package/lib/fio/fioEngine.js +1250 -0
- package/lib/fio/fioError.js +38 -0
- package/lib/fio/fioInfo.js +72 -0
- package/lib/fio/fioPlugin.js +486 -0
- package/lib/fio/fioSchema.js +56 -0
- package/lib/index.js +44 -0
- package/lib/pluginError.js +32 -0
- package/lib/react-native/edge-currency-accountbased.js +239635 -0
- package/lib/react-native/edge-currency-accountbased.js.map +1 -0
- package/lib/react-native-io.js +41 -0
- package/lib/stellar/stellarEngine.js +563 -0
- package/lib/stellar/stellarInfo.js +37 -0
- package/lib/stellar/stellarPlugin.js +215 -0
- package/lib/stellar/stellarSchema.js +54 -0
- package/lib/stellar/stellarTypes.js +66 -0
- package/lib/tezos/tezosEngine.js +497 -0
- package/lib/tezos/tezosInfo.js +60 -0
- package/lib/tezos/tezosPlugin.js +174 -0
- package/lib/tezos/tezosTypes.js +110 -0
- package/lib/xrp/xrpEngine.js +583 -0
- package/lib/xrp/xrpInfo.js +47 -0
- package/lib/xrp/xrpPlugin.js +229 -0
- package/lib/xrp/xrpSchema.js +74 -0
- package/lib/xrp/xrpTypes.js +38 -0
- package/package.json +139 -0
- 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 }
|