@exodus/ethereum-api 4.0.3-alpha1 → 4.0.3

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/LICENSE.md ADDED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "4.0.3-alpha1",
3
+ "version": "4.0.3",
4
4
  "description": "Ethereum Api",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -16,7 +16,7 @@
16
16
  "dependencies": {
17
17
  "@exodus/asset-lib": "^3.7.1",
18
18
  "@exodus/crypto": "^1.0.0-rc.0",
19
- "@exodus/ethereum-lib": "^2.26.1-alpha2",
19
+ "@exodus/ethereum-lib": "^2.26.0",
20
20
  "@exodus/ethereumjs-util": "^7.1.0-exodus.6",
21
21
  "@exodus/fetch": "^1.2.1",
22
22
  "@exodus/simple-retry": "^0.0.6",
@@ -35,5 +35,6 @@
35
35
  },
36
36
  "devDependencies": {
37
37
  "@exodus/models": "^8.10.4"
38
- }
38
+ },
39
+ "gitHead": "1af733c9a00c108adaf9d302ae7a050ee2df8f78"
39
40
  }
@@ -1,28 +1,15 @@
1
- import {
2
- DEFAULT_SERVER_URLS,
3
- ETHEREUM_LIKE_ASSETS,
4
- ETHEREUM_LIKE_MONITOR_TYPES,
5
- } from '@exodus/ethereum-lib'
1
+ import { DEFAULT_SERVER_URLS, ETHEREUM_LIKE_ASSETS } from '@exodus/ethereum-lib'
6
2
 
7
3
  import { create } from './api'
8
4
  import ClarityServer from './clarity'
9
- import ApiCoinNodesServer from './api-coin-nodes'
10
-
11
- function getAssetServer(assetName) {
12
- switch (ETHEREUM_LIKE_MONITOR_TYPES[assetName]) {
13
- case 'no-history':
14
- return new ApiCoinNodesServer({ uri: DEFAULT_SERVER_URLS[assetName] })
15
- case 'clarity':
16
- return new ClarityServer({ baseAssetName: assetName, uri: DEFAULT_SERVER_URLS[assetName] })
17
- case 'magnifier':
18
- return create(DEFAULT_SERVER_URLS[assetName], assetName)
19
- default:
20
- throw new Error(`No server API found for ${assetName}`)
21
- }
22
- }
23
5
 
24
6
  const serverMap = Object.fromEntries(
25
- ETHEREUM_LIKE_ASSETS.map((assetName) => [assetName, getAssetServer(assetName)])
7
+ ETHEREUM_LIKE_ASSETS.map((assetName) => [
8
+ assetName,
9
+ DEFAULT_SERVER_URLS[assetName].includes('clarity')
10
+ ? new ClarityServer({ baseAssetName: assetName, uri: DEFAULT_SERVER_URLS[assetName] })
11
+ : create(DEFAULT_SERVER_URLS[assetName], assetName),
12
+ ])
26
13
  )
27
14
 
28
15
  // allow self-signed certs
@@ -8,4 +8,3 @@ export * from './fantom'
8
8
  export * from './harmony'
9
9
  export * from './ethereumarbnova'
10
10
  export * from './ethereumarbone'
11
- export * from './rootstock'
@@ -1,4 +1,4 @@
1
- import { ETHEREUM_LIKE_MONITOR_TYPES, isRpcBalanceAsset } from '@exodus/ethereum-lib'
1
+ import { isRpcBalanceAsset } from '@exodus/ethereum-lib'
2
2
 
3
3
  const fixBalance = ({ txLog, balance }) => {
4
4
  for (const tx of txLog) {
@@ -23,10 +23,7 @@ const fixBalance = ({ txLog, balance }) => {
23
23
  * @returns {{balance}|null} an object with the balance or null if the balance is unknown/zero
24
24
  */
25
25
  export const getBalances = ({ asset, txLog, accountState }) => {
26
- if (
27
- isRpcBalanceAsset(asset) ||
28
- ETHEREUM_LIKE_MONITOR_TYPES[asset.baseAsset.name] === 'no-history'
29
- ) {
26
+ if (isRpcBalanceAsset(asset)) {
30
27
  const balance =
31
28
  asset.baseAsset.name === asset.name
32
29
  ? accountState?.balance
@@ -1,3 +1,2 @@
1
1
  export * from './ethereum-monitor'
2
- export * from './ethereum-no-history-monitor'
3
2
  export { ClarityMonitor } from './clarity-monitor'
@@ -1,7 +1,7 @@
1
1
  import ms from 'ms'
2
2
  import getSenderNonceKey from './get-sender-nonce-key'
3
3
 
4
- export const UNCONFIRMED_TX_LIMIT = ms('5m')
4
+ const UNCONFIRMED_TX_LIMIT = ms('5m')
5
5
 
6
6
  const mapToObject = (map) => Object.fromEntries([...map.entries()]) // only for string keys
7
7
 
@@ -1,191 +0,0 @@
1
- import { bufferToHex } from '@exodus/ethereumjs-util'
2
- import SolidityContract from '@exodus/solidity-contract'
3
- import EventEmitter from 'events'
4
-
5
- export default class ApiCoinNodesServer extends EventEmitter {
6
- constructor({ uri }) {
7
- super()
8
- this.uri = uri
9
- this.id = 0
10
- }
11
-
12
- setURI(uri) {
13
- this.uri = uri
14
- }
15
-
16
- async request(body) {
17
- const options = {
18
- method: 'POST',
19
- headers: { 'Content-Type': 'application/json' },
20
- body: JSON.stringify(body),
21
- }
22
-
23
- const response = await fetch(this.uri, options)
24
- const data = await response.json()
25
-
26
- return data
27
- }
28
-
29
- async sendBatchRequest(batch) {
30
- const responses = await this.request(batch)
31
- const isValid = responses.every((response) => {
32
- return !isNaN(response?.id) && response?.result
33
- })
34
- if (responses.length !== batch.length || !isValid) {
35
- throw new Error('Bad rpc batch response')
36
- }
37
- const keyed = responses.reduce((acc, response) => {
38
- return { ...acc, [`${response.id}`]: response.result }
39
- }, {})
40
- return batch.map((request) => keyed[`${request.id}`])
41
- }
42
-
43
- async sendRequest(request) {
44
- const response = await this.request(request)
45
-
46
- const result = response?.result
47
- const error = response?.error
48
- if (error || !result) {
49
- const message = error?.message || error?.code || 'no result'
50
- throw new Error(`Bad rpc response: ${message}`)
51
- }
52
- return result
53
- }
54
-
55
- async isContract(address) {
56
- const code = await this.getCode(address)
57
- return code.length > 2
58
- }
59
-
60
- buildRequest({ method, params = [] }) {
61
- return { jsonrpc: '2.0', id: this.id++, method, params }
62
- }
63
-
64
- balanceOfRequest(address, tokenAddress, tag = 'latest') {
65
- const contract = SolidityContract.simpleErc20(tokenAddress)
66
- const callData = contract.balanceOf.build(address)
67
- const data = {
68
- data: bufferToHex(callData),
69
- to: tokenAddress,
70
- }
71
- return this.ethCallRequest(data, tag)
72
- }
73
-
74
- getBalanceRequest(address, tag = 'latest') {
75
- return this.buildRequest({ method: 'eth_getBalance', params: [address, tag] })
76
- }
77
-
78
- gasPriceRequest() {
79
- return this.buildRequest({ method: 'eth_gasPrice' })
80
- }
81
-
82
- estimateGasRequest(data, tag = 'latest') {
83
- return this.buildRequest({ method: 'eth_estimateGas', params: [data, tag] })
84
- }
85
-
86
- sendRawTransactionRequest(data) {
87
- const hex = data.startsWith('0x') ? data : '0x' + data
88
- return this.buildRequest({ method: 'eth_sendRawTransaction', params: [hex] })
89
- }
90
-
91
- getCodeRequest(address, tag = 'latest') {
92
- return this.buildRequest({ method: 'eth_getCode', params: [address, tag] })
93
- }
94
-
95
- getTransactionCountRequest(address, tag = 'latest') {
96
- return this.buildRequest({ method: 'eth_getTransactionCount', params: [address, tag] })
97
- }
98
-
99
- getTransactionByHashRequest(hash) {
100
- return this.buildRequest({ method: 'eth_getTransactionByHash', params: [hash] })
101
- }
102
-
103
- getTransactionReceiptRequest(txhash) {
104
- return this.buildRequest({ method: 'eth_getTransactionReceipt', params: [txhash] })
105
- }
106
-
107
- ethCallRequest(data, tag) {
108
- return this.buildRequest({ method: 'eth_call', params: [data, tag] })
109
- }
110
-
111
- blockNumberRequest() {
112
- return this.buildRequest({ method: 'eth_blockNumber' })
113
- }
114
-
115
- getBlockByNumberRequest(numberHex, isFullTxs = false) {
116
- return this.buildRequest({ method: 'eth_getBlockByNumber', params: [numberHex, isFullTxs] })
117
- }
118
-
119
- simulateRawTransactionRequest(rawTx, applyPending = true) {
120
- const replaced = rawTx.replace('0x', '')
121
- return this.buildRequest({
122
- method: 'debug_simulateRawTransaction',
123
- params: [replaced, applyPending],
124
- })
125
- }
126
-
127
- async balanceOf(...params) {
128
- const request = this.balanceOfRequest(...params)
129
- return this.sendRequest(request)
130
- }
131
-
132
- async getBalance(...params) {
133
- const request = this.getBalanceRequest(...params)
134
- return this.sendRequest(request)
135
- }
136
-
137
- async gasPrice(...params) {
138
- const request = this.gasPriceRequest(...params)
139
- return this.sendRequest(request)
140
- }
141
-
142
- async estimateGas(...params) {
143
- const request = this.estimateGasRequest(...params)
144
- return this.sendRequest(request)
145
- }
146
-
147
- async sendRawTransaction(...params) {
148
- const request = this.sendRawTransactionRequest(...params)
149
- return this.sendRequest(request)
150
- }
151
-
152
- async getCode(...params) {
153
- const request = this.getCodeRequest(...params)
154
- return this.sendRequest(request)
155
- }
156
-
157
- async getTransactionCount(...params) {
158
- const request = this.getTransactionCountRequest(...params)
159
- return this.sendRequest(request)
160
- }
161
-
162
- async getTransactionByHash(...params) {
163
- const request = this.getTransactionByHashRequest(...params)
164
- return this.sendRequest(request)
165
- }
166
-
167
- async getTransactionReceipt(...params) {
168
- const request = this.getTransactionReceiptRequest(...params)
169
- return this.sendRequest(request)
170
- }
171
-
172
- async ethCall(...params) {
173
- const request = this.ethCallRequest(...params)
174
- return this.sendRequest(request)
175
- }
176
-
177
- async blockNumber(...params) {
178
- const request = this.blockNumberRequest(...params)
179
- return this.sendRequest(request)
180
- }
181
-
182
- async getBlockByNumber(...params) {
183
- const request = this.getBlockByNumberRequest(...params)
184
- return this.sendRequest(request)
185
- }
186
-
187
- async simulateRawTransaction(...params) {
188
- const request = this.simulateRawTransactionRequest(...params)
189
- return this.sendRequest(request)
190
- }
191
- }
@@ -1,14 +0,0 @@
1
- import { EthereumLikeFeeMonitor } from '@exodus/ethereum-lib'
2
- import { getServerByName } from '../exodus-eth-server'
3
-
4
- const assetName = 'rootstock'
5
-
6
- export class RootstockFeeMonitor extends EthereumLikeFeeMonitor {
7
- constructor({ updateFee }) {
8
- super({
9
- updateFee,
10
- assetName,
11
- getGasPrice: getServerByName(assetName).gasPrice,
12
- })
13
- }
14
- }
@@ -1,193 +0,0 @@
1
- import BN from 'bn.js'
2
- import { getServer } from '@exodus/ethereum-api'
3
- import { Tx } from '@exodus/models'
4
-
5
- import { getDeriveDataNeededForTick, getDeriveTransactionsToCheck } from './monitor-utils'
6
-
7
- import { isEmpty, unionBy, zipObject } from 'lodash'
8
-
9
- import { BaseMonitor } from '@exodus/asset-lib'
10
-
11
- import { UNCONFIRMED_TX_LIMIT } from './monitor-utils/get-derive-transactions-to-check'
12
-
13
- // The base ethereum monitor no history class handles listening for assets with no history
14
-
15
- export class EthereumNoHistoryMonitor extends BaseMonitor {
16
- constructor({ server, config, ...args }) {
17
- super(args)
18
- this.server = server || getServer(this.asset)
19
- this.config = { ...config }
20
- this.deriveDataNeededForTick = getDeriveDataNeededForTick(this.aci)
21
- this.deriveTransactionsToCheck = getDeriveTransactionsToCheck({
22
- getTxLog: (...args) => this.aci.getTxLog(...args),
23
- })
24
- }
25
-
26
- setServer(config) {
27
- if (config.server === this.server?.uri) {
28
- return
29
- }
30
- this.server.setURI(config.server)
31
- }
32
-
33
- async getBalances({ tokens, ourWalletAddress }) {
34
- const batch = {}
35
- const request = this.server.getBalanceRequest(ourWalletAddress)
36
- batch[this.asset.name] = request
37
- for (const token of tokens) {
38
- const request = this.server.balanceOfRequest(ourWalletAddress, token.contract.address)
39
- batch[token.name] = request
40
- }
41
- const pairs = Object.entries(batch)
42
- if (!pairs.length) {
43
- return {}
44
- }
45
- const requests = pairs.map((pair) => pair[1])
46
- const responses = await this.server.sendBatchRequest(requests)
47
- const entries = pairs.map((pair, idx) => {
48
- const balanceHex = responses[idx]
49
- const name = pair[0]
50
- const hex = balanceHex.startsWith('0x') ? balanceHex.slice(2) : balanceHex
51
- const balance = new BN(hex, 'hex').toString()
52
- return [name, balance]
53
- })
54
- return Object.fromEntries(entries)
55
- }
56
-
57
- async getNewAccountState({ tokens, ourWalletAddress }) {
58
- const asset = this.asset
59
- const newAccountState = {}
60
- const balances = await this.getBalances({ tokens, ourWalletAddress })
61
- const balance = balances[asset.name]
62
- newAccountState.balance = asset.currency.baseUnit(balance)
63
- const tokenBalancePairs = Object.entries(balances).filter((entry) => entry[0] !== asset.name)
64
- const entries = tokenBalancePairs
65
- .map((pair) => {
66
- const token = tokens.find((token) => token.name === pair[0])
67
- const value = token.currency.baseUnit(pair[1] || 0)
68
- return value.isZero ? null : [token.name, value]
69
- })
70
- .filter((pair) => pair)
71
- const tokenBalances = Object.fromEntries(entries)
72
- if (!isEmpty(tokenBalances)) newAccountState.tokenBalances = tokenBalances
73
- return newAccountState
74
- }
75
-
76
- async deriveData({ assetSource, tokens }) {
77
- const { assetName, walletAccount } = assetSource
78
-
79
- const {
80
- ourWalletAddress,
81
- currentAccountState,
82
- minimumConfirmations,
83
- } = await this.deriveDataNeededForTick({ assetName, walletAccount })
84
- const transactionsToCheck = await this.deriveTransactionsToCheck({
85
- assetName,
86
- walletAccount,
87
- tokens,
88
- ourWalletAddress,
89
- })
90
-
91
- return {
92
- ourWalletAddress,
93
- currentAccountState,
94
- minimumConfirmations,
95
- ...transactionsToCheck,
96
- }
97
- }
98
-
99
- async getTransactionsFromNode(transactions) {
100
- const batch = []
101
- for (const { tx } of transactions) {
102
- const request = this.server.getTransactionByHashRequest(tx.txId)
103
- batch.push(request)
104
- }
105
- const responses = await this.server.sendBatchRequest(batch)
106
- const txIds = transactions.map(({ tx }) => tx.txId)
107
- return zipObject(txIds, responses)
108
- }
109
-
110
- async checkPendingTransactions({
111
- pendingTransactionsGroupedByAddressAndNonce,
112
- pendingTransactionsToCheck,
113
- walletAccount,
114
- }) {
115
- const txsToUpdate = []
116
- const txsToRemove = []
117
- const now = Date.now()
118
-
119
- const pendingTransactions = unionBy(
120
- Object.values(pendingTransactionsGroupedByAddressAndNonce),
121
- Object.values(pendingTransactionsToCheck),
122
- 'tx.txId'
123
- )
124
-
125
- if (isEmpty(pendingTransactions))
126
- return {
127
- txsToUpdate,
128
- txsToRemove,
129
- }
130
-
131
- const txsFromNode = await this.getTransactionsFromNode(pendingTransactions)
132
-
133
- for (const { tx, assetName } of pendingTransactions) {
134
- const txFromNode = txsFromNode[tx.txId]
135
- if (now - tx.date.getTime() > UNCONFIRMED_TX_LIMIT) {
136
- txsToRemove.push({
137
- tx,
138
- assetSource: { asset: assetName, walletAccount },
139
- })
140
- } else if (txFromNode.blockHash !== null) {
141
- txsToUpdate.push({
142
- tx: { ...tx, confirmations: 1 },
143
- assetSource: { asset: assetName, walletAccount },
144
- })
145
- }
146
- }
147
-
148
- return {
149
- txsToUpdate,
150
- txsToRemove,
151
- }
152
- }
153
-
154
- async tick({ refresh, walletAccount }) {
155
- const assets = await this.aci.getAssetsForNetwork({ baseAssetName: this.asset.name })
156
- const tokens = Object.values(assets).filter((asset) => asset.baseAsset.name !== asset.name)
157
-
158
- const assetSource = { assetName: this.asset.name, walletAccount }
159
-
160
- const {
161
- ourWalletAddress,
162
- pendingTransactionsGroupedByAddressAndNonce,
163
- pendingTransactionsToCheck,
164
- } = await this.deriveData({
165
- assetSource,
166
- tokens,
167
- })
168
-
169
- const accountState = await this.getNewAccountState({
170
- tokens,
171
- ourWalletAddress,
172
- })
173
-
174
- const { txsToUpdate, txsToRemove } = await this.checkPendingTransactions({
175
- pendingTransactionsGroupedByAddressAndNonce,
176
- pendingTransactionsToCheck,
177
- walletAccount,
178
- })
179
-
180
- const logItemsByAsset = {}
181
-
182
- txsToUpdate.forEach((txToUpdate) => {
183
- logItemsByAsset[txToUpdate.assetSource.asset] =
184
- logItemsByAsset[txToUpdate.assetSource.asset] || []
185
- logItemsByAsset[txToUpdate.assetSource.asset].push(Tx.fromJSON(txToUpdate.tx))
186
- })
187
-
188
- await this.updateAccountState({ newData: { ...accountState }, walletAccount })
189
-
190
- await this.removeFromTxLog(txsToRemove)
191
- await this.updateTxLogByAsset({ logItemsByAsset, walletAccount, refresh })
192
- }
193
- }