@exodus/ethereum-api 8.1.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 +2 -2
- package/src/allowance/index.js +2 -6
- package/src/create-asset.js +2 -0
- package/src/eth-like-util.js +12 -0
- package/src/gas-estimation.js +9 -9
- package/src/get-fee-async.js +97 -0
- package/src/get-fee.js +2 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.2.0",
|
|
4
4
|
"description": "Ethereum Api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"cross-fetch": "^3.1.5",
|
|
59
59
|
"delay": "4.0.1"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "3d6deb1f1d8ba08510be92ed52dfe39ce69e6fb9"
|
|
62
62
|
}
|
package/src/allowance/index.js
CHANGED
|
@@ -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) {
|
package/src/create-asset.js
CHANGED
|
@@ -38,6 +38,7 @@ import { createGetBalanceForAddress } from './get-balance-for-address'
|
|
|
38
38
|
import { estimateL1DataFeeFactory, getL1GetFeeFactory } from './optimism-gas'
|
|
39
39
|
import { mapValues } from 'lodash'
|
|
40
40
|
import { createWeb3API } from './web3'
|
|
41
|
+
import getFeeAsyncFactory from './get-fee-async'
|
|
41
42
|
|
|
42
43
|
export const createAssetFactory = ({
|
|
43
44
|
assetsList,
|
|
@@ -218,6 +219,7 @@ export const createAssetFactory = ({
|
|
|
218
219
|
getBalanceForAddress: createGetBalanceForAddress({ asset, server }),
|
|
219
220
|
getConfirmationsNumber: () => confirmationNumber,
|
|
220
221
|
getDefaultAddressPath: () => defaultAddressPath,
|
|
222
|
+
getFeeAsync: getFeeAsyncFactory({ assetClientInterface, gasLimit, createUnsignedTx }),
|
|
221
223
|
getFee,
|
|
222
224
|
getFeeData: () => feeData,
|
|
223
225
|
getKeyIdentifier: createGetKeyIdentifier({
|
package/src/eth-like-util.js
CHANGED
|
@@ -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)
|
package/src/gas-estimation.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
76
|
-
isContract = await isContractAddress({ asset, address: toAddress })
|
|
77
|
-
if (!isContract) return defaultGasLimit()
|
|
74
|
+
return defaultGasLimit()
|
|
78
75
|
}
|
|
79
76
|
|
|
80
|
-
|
|
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,
|
|
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 =
|
|
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
|