@exodus/ethereum-api 8.0.0 → 8.2.0

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": "8.0.0",
3
+ "version": "8.2.0",
4
4
  "description": "Ethereum Api",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -34,6 +34,7 @@
34
34
  "@exodus/models": "^11.0.0",
35
35
  "@exodus/simple-retry": "^0.0.6",
36
36
  "@exodus/solidity-contract": "^1.1.3",
37
+ "@exodus/web3-ethereum-utils": "^3.27.1",
37
38
  "bn.js": "^5.2.1",
38
39
  "events": "^1.1.1",
39
40
  "idna-uts46-hx": "^2.3.1",
@@ -57,5 +58,5 @@
57
58
  "cross-fetch": "^3.1.5",
58
59
  "delay": "4.0.1"
59
60
  },
60
- "gitHead": "a5992c8a158d486d6e95c03b17136b67ee06459c"
61
+ "gitHead": "3d6deb1f1d8ba08510be92ed52dfe39ce69e6fb9"
61
62
  }
@@ -30,15 +30,13 @@ export async function isSpendingApprovalRequired({
30
30
  export const createApprove =
31
31
  ({ sendTx }) =>
32
32
  async ({
33
- walletAccount,
34
- fromAddress,
35
33
  spenderAddress,
36
34
  asset,
37
35
  feeData,
38
- feeAmount,
39
36
  approveAmount,
40
37
  gasLimit,
41
38
  txInput: preTxInput,
39
+ ...rest
42
40
  }) => {
43
41
  const baseAsset = asset.baseAsset
44
42
  const txInput =
@@ -48,7 +46,6 @@ export const createApprove =
48
46
  const gasLimitOptions = {
49
47
  asset: baseAsset,
50
48
  amount: baseAsset.currency.ZERO,
51
- fromAddress,
52
49
  toAddress,
53
50
  txInput,
54
51
  feeData,
@@ -59,7 +56,6 @@ export const createApprove =
59
56
 
60
57
  const txData = await sendTx({
61
58
  asset: baseAsset.name,
62
- walletAccount,
63
59
  receiver: {
64
60
  address: toAddress,
65
61
  amount: baseAsset.currency.ZERO,
@@ -67,8 +63,8 @@ export const createApprove =
67
63
  txInput,
68
64
  gasPrice: feeData.gasPrice,
69
65
  gasLimit,
70
- feeAmount,
71
66
  silent: true,
67
+ ...rest,
72
68
  })
73
69
 
74
70
  if (!txData || !txData.txId) {
@@ -37,6 +37,8 @@ import { serverBasedFeeMonitorFactoryFactory } from './server-based-fee-monitor'
37
37
  import { createGetBalanceForAddress } from './get-balance-for-address'
38
38
  import { estimateL1DataFeeFactory, getL1GetFeeFactory } from './optimism-gas'
39
39
  import { mapValues } from 'lodash'
40
+ import { createWeb3API } from './web3'
41
+ import getFeeAsyncFactory from './get-fee-async'
40
42
 
41
43
  export const createAssetFactory = ({
42
44
  assetsList,
@@ -217,6 +219,7 @@ export const createAssetFactory = ({
217
219
  getBalanceForAddress: createGetBalanceForAddress({ asset, server }),
218
220
  getConfirmationsNumber: () => confirmationNumber,
219
221
  getDefaultAddressPath: () => defaultAddressPath,
222
+ getFeeAsync: getFeeAsyncFactory({ assetClientInterface, gasLimit, createUnsignedTx }),
220
223
  getFee,
221
224
  getFeeData: () => feeData,
222
225
  getKeyIdentifier: createGetKeyIdentifier({
@@ -239,6 +242,7 @@ export const createAssetFactory = ({
239
242
  signer ? signMessageWithSigner({ message, signer }) : signMessage({ privateKey, message }),
240
243
  ...(supportsStaking && { staking: createStakingApi({ network: asset.name }) }),
241
244
  validateAssetId: address.validate,
245
+ web3: createWeb3API({ asset }),
242
246
  }
243
247
 
244
248
  const fullAsset = {
@@ -13,6 +13,12 @@ export async function isContractAddress({ asset, address }) {
13
13
  return getServer(asset).isContract(address)
14
14
  }
15
15
 
16
+ export const isContractAddressCached = memoizeLruCache(
17
+ isContractAddress,
18
+ ({ asset, address }) => `${asset.name}:${address}`,
19
+ { max: 1000 }
20
+ )
21
+
16
22
  export async function isForwarderContract({ asset, address }) {
17
23
  const contractCode = await getServer(asset).getCode(address)
18
24
  return (
@@ -21,6 +27,12 @@ export async function isForwarderContract({ asset, address }) {
21
27
  )
22
28
  }
23
29
 
30
+ export const isForwarderContractCached = memoizeLruCache(
31
+ isForwarderContract,
32
+ ({ asset, address }) => `${asset.name}:${address}`,
33
+ { max: 1000 }
34
+ )
35
+
24
36
  export async function getNonce({ asset, address, tag = 'latest' }) {
25
37
  const server = getServer(asset)
26
38
  const nonce = await server.getTransactionCount(address, tag)
@@ -1,7 +1,7 @@
1
1
  import BN from 'bn.js'
2
2
  import * as ethUtil from '@exodus/ethereumjs-util'
3
3
  import { currency2buffer, isToken } from '@exodus/ethereum-lib'
4
- import { estimateGas, isContractAddress } from './eth-like-util'
4
+ import { estimateGas, isContractAddressCached } from './eth-like-util'
5
5
 
6
6
  const EXTRA_PERCENTAGE = 20
7
7
 
@@ -44,9 +44,7 @@ export async function fetchGasLimit({
44
44
  toAddress,
45
45
  txInput = '0x',
46
46
  amount,
47
- feeData = {},
48
47
  bip70,
49
- isContract,
50
48
  throwOnError = true,
51
49
  extraPercentage,
52
50
  }) {
@@ -54,13 +52,14 @@ export async function fetchGasLimit({
54
52
  return asset.name === 'ethereum' ? 65_000 : 130_000 // from on chain stats https://dune.xyz/queries/189123
55
53
 
56
54
  if (!amount) amount = asset.currency.ZERO
57
- // eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only
58
- if (!feeData.gasPrice) feeData.gasPrice = asset.baseAsset.currency.ZERO
59
55
 
60
56
  const defaultGasLimit = () =>
61
57
  asset.gasLimit + GAS_PER_NON_ZERO_BYTE * ethUtil.toBuffer(txInput).length
62
58
 
63
59
  const _isToken = isToken(asset)
60
+
61
+ const isContract = await isContractAddressCached({ asset, address: toAddress })
62
+
64
63
  if (_isToken) {
65
64
  // only create tx-input only if not pass tx-input to a token asset
66
65
  if (txInput === '0x') {
@@ -72,12 +71,13 @@ export async function fetchGasLimit({
72
71
  amount = asset.baseAsset.currency.ZERO
73
72
  toAddress = asset.contract.address
74
73
  } else if (!isContract && !['ethereumarbone', 'ethereumarbnova'].includes(asset.name)) {
75
- if (isContract === undefined)
76
- isContract = await isContractAddress({ asset, address: toAddress })
77
- if (!isContract) return defaultGasLimit()
74
+ return defaultGasLimit()
78
75
  }
79
76
 
80
- const gasPrice = ethUtil.bufferToHex(currency2buffer(feeData.gasPrice))
77
+ // calling forwarder contracts with a bumped gas limit causes 'Out Of Gas' error on chain
78
+ // Since geth v1.9.14 estimateGas will throw if user does not have enough ETH.
79
+ // If gasPrice is set to zero, estimateGas will make the expected estimation.
80
+ const gasPrice = ethUtil.bufferToHex(currency2buffer(asset.baseAsset.currency.ZERO))
81
81
 
82
82
  try {
83
83
  return await estimateGasLimit(
@@ -0,0 +1,97 @@
1
+ import assert from 'minimalistic-assert'
2
+ import { fetchGasLimit } from './gas-estimation'
3
+ import { isForwarderContractCached } from './eth-like-util'
4
+ import { getFeeFactory } from './get-fee'
5
+
6
+ const FIXED_TRANSFER_GAS_LIMIT_ASSETS = new Set([
7
+ 'amp',
8
+ 'tetherusd',
9
+ 'usdcoin',
10
+ 'snx',
11
+ 'geminidollar',
12
+ ])
13
+
14
+ async function resolveExtraPercentage({ asset, toAddress }) {
15
+ const isToken = asset.name !== asset.baseAsset.name
16
+ // calling forwarder contracts with a bumped gas limit causes 'Out Of Gas' error on chain
17
+ const hasForwarderContract =
18
+ !isToken && toAddress ? await isForwarderContractCached({ asset, address: toAddress }) : false
19
+
20
+ return asset.baseAsset.estimateL1DataFee
21
+ ? 100
22
+ : (isToken && FIXED_TRANSFER_GAS_LIMIT_ASSETS.has(asset.name)) || hasForwarderContract
23
+ ? 0
24
+ : undefined
25
+ }
26
+
27
+ const getFeeAsyncFactory = ({
28
+ assetClientInterface,
29
+ gasLimit: defaultGasLimit,
30
+ createUnsignedTx,
31
+ }) => {
32
+ assert(assetClientInterface, 'assetClientInterface is required')
33
+ assert(createUnsignedTx, 'createUnsignedTx is required')
34
+ const getFee = getFeeFactory({ gasLimit: defaultGasLimit })
35
+ return async ({
36
+ asset,
37
+ fromAddress = '0xffffffffffffffffffffffffffffffffffffffff', // sending from a random address
38
+ toAddress = '0xffffffffffffffffffffffffffffffffffffffff', // sending to a random address,
39
+ amount,
40
+ bip70,
41
+ txInput,
42
+ isExchange,
43
+ customFee,
44
+ calculateEffectiveFee,
45
+ isSendAll,
46
+ isRbfAllowed = true, // Destkop, isRbfAllowed=true when advanced panel is on
47
+ throwOnError = false,
48
+ }) => {
49
+ const extraPercentage = await resolveExtraPercentage({ asset, toAddress })
50
+
51
+ const gasLimit = await fetchGasLimit({
52
+ asset,
53
+ fromAddress,
54
+ toAddress,
55
+ txInput: txInput ?? '0x', // default value is '0x'
56
+ amount,
57
+ bip70,
58
+ throwOnError,
59
+ extraPercentage,
60
+ })
61
+ const feeData = await assetClientInterface.getFeeConfig({ assetName: asset.baseAsset.name })
62
+
63
+ const optimismL1DataFee = asset.baseAsset.estimateL1DataFee
64
+ ? await asset.baseAsset.estimateL1DataFee({
65
+ unsignedTx: createUnsignedTx({
66
+ asset,
67
+ address: toAddress,
68
+ fromAddress,
69
+ amount,
70
+ nonce: 0,
71
+ txInput,
72
+ gasPrice: feeData.gasPrice,
73
+ gasLimit,
74
+ }),
75
+ })
76
+ : undefined
77
+
78
+ const { fee, ...rest } = getFee({
79
+ asset,
80
+ feeData,
81
+ gasLimit,
82
+ isExchange,
83
+ isSendAll,
84
+ amount,
85
+ isRbfAllowed,
86
+ calculateEffectiveFee, // BE
87
+ customFee, // BE
88
+ })
89
+
90
+ const l1DataFee = optimismL1DataFee
91
+ ? asset.baseAsset.currency.baseUnit(optimismL1DataFee)
92
+ : asset.baseAsset.currency.ZERO
93
+ return { fee: fee.add(l1DataFee), gasLimit, ...rest }
94
+ }
95
+ }
96
+
97
+ export default getFeeAsyncFactory
package/src/get-fee.js CHANGED
@@ -44,8 +44,7 @@ export const getFeeFactory =
44
44
  isSendAll,
45
45
  amount,
46
46
  isRbfAllowed = true, // Destkop, isRbfAllowed=true when advanced panel is on
47
- calculateEffectiveFee, // BE
48
- customFee: customGasPrice, // BE
47
+ calculateEffectiveFee,
49
48
  }) => {
50
49
  const { gasPrice, eip1559Enabled, baseFeePerGas, tipGasPrice } = feeData
51
50
  const gasPriceMultiplier = getGasPriceMultiplier({
@@ -58,7 +57,7 @@ export const getFeeFactory =
58
57
 
59
58
  const extraFeeData = getExtraFeeData({ asset, amount })
60
59
  if (calculateEffectiveFee && eip1559Enabled) {
61
- const maxFeePerGas = customGasPrice || gasPrice
60
+ const maxFeePerGas = customFee || gasPrice
62
61
  // effective_gas_price = min(base_fee_per_gas + tip_gas_price, max_fee_per_gas)
63
62
  const feePerGas = baseFeePerGas.add(tipGasPrice)
64
63
  const effectiveGasPrice = feePerGas.lt(maxFeePerGas) ? feePerGas : maxFeePerGas
@@ -0,0 +1,12 @@
1
+ import { createSimulateMessage as createSimulateEVMMessage } from '@exodus/web3-ethereum-utils'
2
+ import assert from 'minimalistic-assert'
3
+
4
+ export const createSimulateMessage = ({ asset }) => {
5
+ assert('asset', '"asset" should be passed.')
6
+
7
+ const simulateEVMMessage = createSimulateEVMMessage()
8
+
9
+ return function simulateMessage({ message, ...restParameters }) {
10
+ return simulateEVMMessage({ asset, message, ...restParameters })
11
+ }
12
+ }
@@ -0,0 +1,7 @@
1
+ import { createSimulateMessage } from './createSimulateMessage'
2
+
3
+ export const createWeb3API = (deps) => {
4
+ return {
5
+ simulateMessage: createSimulateMessage(deps),
6
+ }
7
+ }