@exodus/ethereum-api 1.0.17 → 2.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "1.0.17",
3
+ "version": "2.0.1",
4
4
  "description": "Ethereum Api",
5
5
  "main": "src/index.js",
6
6
  "author": "Exodus Movement, Inc.",
@@ -10,7 +10,7 @@
10
10
  "access": "restricted"
11
11
  },
12
12
  "dependencies": {
13
- "@exodus/ethereum-lib": "^1.2.2",
13
+ "@exodus/ethereum-lib": "^2.0.1",
14
14
  "@exodus/simple-retry": "^0.0.6",
15
15
  "fetchival": "0.3.3",
16
16
  "lodash": "^4.17.11",
@@ -20,5 +20,5 @@
20
20
  "url-join": "4.0.0",
21
21
  "ws": "6.1.0"
22
22
  },
23
- "gitHead": "0c3b4952a99f9157becc9a3754a34e289a0a1095"
23
+ "gitHead": "7708ea7f4a1a441c910143dece98fe712048670f"
24
24
  }
@@ -0,0 +1,47 @@
1
+ import { get } from 'lodash'
2
+ import { eth, etc } from './exodus-eth-server'
3
+
4
+ export async function isContract(assetName, address) {
5
+ const server = assetName === 'ethereumclassic' ? etc : eth
6
+ return server.isContract(address)
7
+ }
8
+
9
+ export async function getNonce({ asset, address, tag = 'latest' }) {
10
+ if (!['ethereum', 'ethereumclassic'].includes(asset.name)) return
11
+ const server = asset.name === 'ethereumclassic' ? etc : eth
12
+ const nonce = await server.getTransactionCount(address, tag)
13
+ return parseInt(nonce, 16)
14
+ }
15
+
16
+ export async function estimateGas({ asset, ...args }) {
17
+ const server = asset.name === 'ethereumclassic' ? etc : eth
18
+ return server.estimateGas(args)
19
+ }
20
+
21
+ const fetchEthereumBalance = async (address, balanceField = 'value') => {
22
+ const balances = await eth.getBalance(address)
23
+ return get(balances, ['confirmed', balanceField], '0')
24
+ }
25
+
26
+ const fetchEthereumClassicBalance = async (address) => {
27
+ const balances = await etc.getBalance(address)
28
+ return get(balances, 'confirmed.value', '0')
29
+ }
30
+
31
+ // Only Ethereum and Ethereumclassic, not ERC20
32
+ export async function getBalance({ asset, address }) {
33
+ const _getBalance =
34
+ asset.name === 'ethereumclassic' ? fetchEthereumClassicBalance : fetchEthereumBalance
35
+ return _getBalance(address)
36
+ }
37
+
38
+ // Only Ethereum ERC20, not Ethereumclassic
39
+ export async function getTokenBalance({ asset, address }) {
40
+ return fetchEthereumBalance(address, asset.contract.address.toLowerCase())
41
+ }
42
+
43
+ // Returns function for supplied asset
44
+ export function sendRawTransaction(asset) {
45
+ const server = asset.name === 'ethereumclassic' ? etc : eth
46
+ return server.sendRawTransaction
47
+ }
@@ -28,6 +28,7 @@ export function create(defaultURL) {
28
28
  const msg = data.error.replace(/^RPC error \(code: -\d+\): /, '')
29
29
  nerr = new Error(msg)
30
30
  } catch (err) {}
31
+ nerr.finalError = true
31
32
  }
32
33
 
33
34
  throw nerr
@@ -55,12 +56,12 @@ export function create(defaultURL) {
55
56
 
56
57
  async getBalance(address, opts) {
57
58
  opts = { startblock: 'earliest', endblock: 'pending', ...opts }
58
- return request('balance', { address, from: opts.startblock, to: opts.endblock })
59
+ return requestWithRetry('balance', { address, from: opts.startblock, to: opts.endblock })
59
60
  },
60
61
 
61
62
  async getHistory(address, opts) {
62
63
  opts = { startblock: 'earliest', endblock: 'pending', limit: 1000, ...opts }
63
- return request('history', {
64
+ return requestWithRetry('history', {
64
65
  address,
65
66
  from: opts.startblock,
66
67
  to: opts.endblock,
@@ -73,27 +74,32 @@ export function create(defaultURL) {
73
74
  },
74
75
 
75
76
  async getTransactionCount(address, tag = 'latest') {
76
- return request('proxy', { method: 'eth_getTransactionCount', address, tag })
77
+ return requestWithRetry('proxy', { method: 'eth_getTransactionCount', address, tag })
77
78
  },
78
79
 
79
80
  async getTransactionByHash(hash) {
80
- return request('proxy', { method: 'eth_getTransactionByHash', hash })
81
+ return requestWithRetry('proxy', { method: 'eth_getTransactionByHash', hash })
81
82
  },
82
83
 
83
84
  async getTransactionReceipt(txhash) {
84
- return request('proxy', { method: 'eth_getTransactionReceipt', txhash })
85
+ return requestWithRetry('proxy', { method: 'eth_getTransactionReceipt', txhash })
85
86
  },
86
87
 
87
88
  async getCode(address, tag = 'latest') {
88
- return request('proxy', { method: 'eth_getCode', address, tag })
89
+ return requestWithRetry('proxy', { method: 'eth_getCode', address, tag })
90
+ },
91
+
92
+ async isContract(address) {
93
+ const code = await requestWithRetry('proxy', { method: 'eth_getCode', address })
94
+ return code.length > 2
89
95
  },
90
96
 
91
97
  async sendRawTransaction(data) {
92
- return request('proxy', { method: 'eth_sendRawTransaction', hex: '0x' + data })
98
+ return requestWithRetry('proxy', { method: 'eth_sendRawTransaction', hex: '0x' + data })
93
99
  },
94
100
 
95
101
  async estimateGas(data, tag = 'latest') {
96
- return request('proxy', { method: 'eth_estimateGas', ...data, tag })
102
+ return requestWithRetry('proxy', { method: 'eth_estimateGas', ...data, tag })
97
103
  },
98
104
 
99
105
  async ethCall(data, tag = 'latest') {
@@ -101,15 +107,19 @@ export function create(defaultURL) {
101
107
  },
102
108
 
103
109
  async blockNumber() {
104
- return request('proxy', { method: 'eth_blockNumber' })
110
+ return requestWithRetry('proxy', { method: 'eth_blockNumber' })
105
111
  },
106
112
 
107
113
  async getBlockByNumber(number, isFullTransactions = false) {
108
- return request('proxy', { method: 'eth_getBlockByNumber', number, isFullTransactions })
114
+ return requestWithRetry('proxy', {
115
+ method: 'eth_getBlockByNumber',
116
+ number,
117
+ isFullTransactions,
118
+ })
109
119
  },
110
120
 
111
121
  async getLogs(params) {
112
- return request('proxy', { method: 'eth_getLogs', ...params })
122
+ return requestWithRetry('proxy', { method: 'eth_getLogs', ...params })
113
123
  },
114
124
 
115
125
  async simulateRawTransaction(rawTransaction, applyPending = true) {
@@ -1,7 +1,7 @@
1
1
  import { create } from './api'
2
2
 
3
- const EXODUS_ETH_SERVER_URL = 'https://eth.a.exodus.io/wallet/v1/'
4
- const EXODUS_ETC_SERVER_URL = 'https://etc.a.exodus.io/wallet/v1/'
3
+ const EXODUS_ETH_SERVER_URL = 'https://geth.a.exodus.io/wallet/v1/'
4
+ const EXODUS_ETC_SERVER_URL = 'https://getc.a.exodus.io/wallet/v1/'
5
5
 
6
6
  // allow self-signed certs
7
7
  // process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
@@ -1,38 +1,32 @@
1
1
  import BN from 'bn.js'
2
2
  import * as ethUtil from 'ethereumjs-util'
3
3
  import { currency2buffer, isEthereumToken, getBaseAsset } from '@exodus/ethereum-lib'
4
- import { estimateGas, isContract as _isContract } from './with-fallback'
4
+ import { estimateGas, isContract as _isContract } from './eth-like-util'
5
5
 
6
6
  const EXTRA_PERCENTAGE = 20
7
7
 
8
+ // Starting with geth v1.9.14, if gasPrice is set for eth_estimateGas call, the call allowance will
9
+ // be calculated with account's balance divided by gasPrice. If user's balance is too low,
10
+ // the gasEstimation will fail. If gasPrice is set to '0x0', the account's balance is not
11
+ // used to estimate gas.
8
12
  export async function estimateGasLimit(
9
13
  asset: Object,
10
14
  fromAddress: string,
11
15
  toAddress: string,
12
16
  amount: Buffer | Object,
13
17
  data: Buffer | string,
14
- gasPrice: string,
15
- extraPercentage: number = EXTRA_PERCENTAGE
18
+ gasPrice?: string = '0x',
19
+ extraPercentage?: number = EXTRA_PERCENTAGE
16
20
  ): number {
17
- if (!Buffer.isBuffer(amount)) {
18
- amount = currency2buffer(amount)
19
- }
20
-
21
- amount = ethUtil.bufferToHex(amount)
22
- while (amount[2] === '0') amount = '0x' + amount.slice(3)
23
- if (amount === '0x') amount = '0x0'
24
-
25
- while (gasPrice[2] === '0') gasPrice = '0x' + gasPrice.slice(3)
26
- if (gasPrice === '0x') gasPrice = '0x0'
27
-
28
- const estimatedGas = await estimateGas({
29
- asset,
21
+ const opts = {
30
22
  from: fromAddress,
31
23
  to: toAddress,
32
- value: amount,
24
+ value: normalizeAmount(amount),
33
25
  data: Buffer.isBuffer(data) ? ethUtil.bufferToHex(data) : data,
34
- gasPrice,
35
- })
26
+ gasPrice: normalizeGasPrice(gasPrice),
27
+ }
28
+
29
+ const estimatedGas = await estimateGas({ asset, ...opts })
36
30
  return new BN(estimatedGas.slice(2), 16)
37
31
  .imuln(100 + extraPercentage)
38
32
  .idivn(100)
@@ -97,3 +91,21 @@ export async function fetchGasLimit({
97
91
 
98
92
  return isToken ? asset.gasLimit : asset.contractGasLimit
99
93
  }
94
+
95
+ function normalizeAmount(amount) {
96
+ if (!Buffer.isBuffer(amount)) {
97
+ amount = currency2buffer(amount)
98
+ }
99
+
100
+ amount = ethUtil.bufferToHex(amount)
101
+ while (amount[2] === '0') amount = '0x' + amount.slice(3)
102
+ if (amount === '0x') amount = '0x0'
103
+
104
+ return amount
105
+ }
106
+
107
+ function normalizeGasPrice(gasPrice) {
108
+ while (gasPrice[2] === '0') gasPrice = '0x' + gasPrice.slice(3)
109
+ if (gasPrice === '0x') gasPrice = '0x0'
110
+ return gasPrice
111
+ }
package/src/index.js CHANGED
@@ -1,9 +1,4 @@
1
- import * as etherscan from './etherscan'
2
- import * as ethWithFallback from './eth-with-fallback'
3
- import * as etcWithFallback from './etc-with-fallback'
4
-
5
- export * from './with-fallback'
1
+ export * from './eth-like-util'
6
2
  export * from './fee-monitor'
7
3
  export * from './gas-estimation'
8
4
  export * from './exodus-eth-server'
9
- export { etherscan, ethWithFallback, etcWithFallback }
@@ -1,12 +0,0 @@
1
- import { etc as etcServer } from './exodus-eth-server'
2
- import { withFallback } from './with-fallback'
3
-
4
- export const isContract = async (address) => {
5
- const code = await withFallback(etcServer.getCode, etcServer.getCode)(address)
6
- return code.length > 2
7
- }
8
-
9
- export const sendRawTransaction = withFallback(
10
- etcServer.sendRawTransaction,
11
- etcServer.sendRawTransaction
12
- )
@@ -1,13 +0,0 @@
1
- import { eth as ethServer } from './exodus-eth-server'
2
- import { withFallback } from './with-fallback'
3
- import * as etherscan from './etherscan'
4
-
5
- export const isContract = async (address) => {
6
- const code = await withFallback(ethServer.getCode, etherscan.getCode)(address)
7
- return code.length > 2
8
- }
9
-
10
- export const sendRawTransaction = withFallback(
11
- ethServer.sendRawTransaction,
12
- etherscan.sendRawTransaction
13
- )
@@ -1,87 +0,0 @@
1
- import { get } from 'lodash'
2
- import { eth as ethServer, etc as etcServer } from './exodus-eth-server'
3
- import * as etherscan from './etherscan'
4
-
5
- export function withFallback(fn, fn2) {
6
- return async (...args) => {
7
- try {
8
- return await fn(...args)
9
- } catch (err1) {
10
- try {
11
- return await fn2(...args)
12
- } catch (err2) {
13
- const err = new Error(`${err1.message} | ${err2.message}`)
14
- throw Object.assign(err, { err1, err2 })
15
- }
16
- }
17
- }
18
- }
19
-
20
- export async function isContract(assetName, address) {
21
- const _getCode =
22
- assetName === 'ethereumclassic'
23
- ? withFallback(etcServer.getCode, etcServer.getCode)
24
- : withFallback(ethServer.getCode, etherscan.getCode)
25
-
26
- const code = await _getCode(address)
27
- return code.length > 2
28
- }
29
-
30
- export async function getNonce({ asset, address, tag = 'latest' }) {
31
- if (!['ethereum', 'ethereumclassic'].includes(asset.name)) return
32
-
33
- const _getNonce =
34
- asset.name === 'ethereumclassic'
35
- ? withFallback(etcServer.getTransactionCount, etcServer.getTransactionCount)
36
- : withFallback(ethServer.getTransactionCount, etherscan.getTransactionCount)
37
-
38
- const nonce = await _getNonce(address, tag)
39
- return parseInt(nonce, 16)
40
- }
41
-
42
- export async function estimateGas({ asset, ...args }) {
43
- const _estimateGas =
44
- asset.name === 'ethereumclassic'
45
- ? withFallback(etcServer.estimateGas, etcServer.estimateGas)
46
- : withFallback(ethServer.estimateGas, etherscan.estimateGas)
47
-
48
- return _estimateGas(args)
49
- }
50
-
51
- const fetchEthereumBalance = async (address, balanceField = 'value') => {
52
- const balances = await ethServer.getBalance(address)
53
- return get(balances, ['confirmed', balanceField], '0')
54
- }
55
-
56
- const fetchEthereumClassicBalance = async (address) => {
57
- const balances = await etcServer.getBalance(address)
58
- return get(balances, 'confirmed.value', '0')
59
- }
60
-
61
- // Only Ethereum and Ethereumclassic, not ERC20
62
- export async function getBalance({ asset, address }) {
63
- const _getBalance =
64
- asset.name === 'ethereumclassic'
65
- ? withFallback(fetchEthereumClassicBalance, fetchEthereumClassicBalance)
66
- : withFallback(fetchEthereumBalance, etherscan.fetchBalance)
67
-
68
- return _getBalance(address)
69
- }
70
-
71
- // Only Ethereum ERC20, not Ethereumclassic
72
- export async function getTokenBalance({ asset, address }) {
73
- const _getTokenBalance = withFallback(
74
- async (contractAddress, address) => fetchEthereumBalance(address, contractAddress),
75
- etherscan.tokenBalance
76
- )
77
-
78
- const contractAddress = asset.contract.address.toLowerCase()
79
- return _getTokenBalance(contractAddress, address)
80
- }
81
-
82
- // Returns function for supplied asset
83
- export function sendRawTransaction(asset) {
84
- return asset.name === 'ethereumclassic'
85
- ? withFallback(etcServer.sendRawTransaction, etcServer.sendRawTransaction)
86
- : withFallback(ethServer.sendRawTransaction, etherscan.sendRawTransaction)
87
- }