@exodus/ethereum-api 7.3.2 → 7.5.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/create-asset.js +21 -3
- package/src/exodus-eth-server/api-coin-nodes.js +15 -0
- package/src/exodus-eth-server/api.js +11 -0
- package/src/exodus-eth-server/clarity.js +11 -0
- package/src/fee-monitor/server-based-fee-monitor.js +21 -6
- package/src/get-balance-for-address.js +10 -0
- package/src/get-balances.js +35 -24
- package/src/optimism-gas/index.js +42 -13
- package/src/tx-send/tx-send.js +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.0",
|
|
4
4
|
"description": "Ethereum Api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
"cross-fetch": "^3.1.5",
|
|
58
58
|
"delay": "4.0.1"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "8066c2fd98a9065156d93108e42aa01f40aecf57"
|
|
61
61
|
}
|
package/src/create-asset.js
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
signUnsignedTxWithSigner,
|
|
22
22
|
validate,
|
|
23
23
|
signMessage,
|
|
24
|
+
signMessageWithSigner,
|
|
24
25
|
signHardwareFactory,
|
|
25
26
|
DEFAULT_FEE_MONITOR_INTERVAL,
|
|
26
27
|
} from '@exodus/ethereum-lib'
|
|
@@ -34,8 +35,9 @@ import { getEffectiveGasPrice, getFeeFactory } from './get-fee'
|
|
|
34
35
|
import { createEthereumHooks } from './hooks'
|
|
35
36
|
import { createStakingApi } from './staking-api'
|
|
36
37
|
import { txSendFactory } from './tx-send'
|
|
37
|
-
import { signMessageWithSigner } from '@exodus/ethereum-lib/src/sign-message'
|
|
38
38
|
import { serverBasedFeeMonitorFactoryFactory } from './fee-monitor'
|
|
39
|
+
import { createGetBalanceForAddress } from './get-balance-for-address'
|
|
40
|
+
import { estimateL1DataFeeFactory, getL1GetFeeFactory } from './optimism-gas'
|
|
39
41
|
|
|
40
42
|
export const createAssetFactory = ({
|
|
41
43
|
assetsList,
|
|
@@ -43,7 +45,6 @@ export const createAssetFactory = ({
|
|
|
43
45
|
AccountState: CustomAccountState,
|
|
44
46
|
monitorInterval,
|
|
45
47
|
nfts = false,
|
|
46
|
-
getFee: customGetFee,
|
|
47
48
|
useEip1191ChainIdChecksum = false,
|
|
48
49
|
customTokens = true,
|
|
49
50
|
isTestnet = false,
|
|
@@ -53,6 +54,7 @@ export const createAssetFactory = ({
|
|
|
53
54
|
customBip44,
|
|
54
55
|
customCreateGetKeyIdentifier,
|
|
55
56
|
feeMonitorInterval,
|
|
57
|
+
l1GasOracleAddress, // l1 extra fee for base and opto
|
|
56
58
|
}) => {
|
|
57
59
|
assert(assetsList, 'assetsList is required')
|
|
58
60
|
assert(feeData, 'feeData is required')
|
|
@@ -114,6 +116,7 @@ export const createAssetFactory = ({
|
|
|
114
116
|
const createFeeMonitor = serverBasedFeeMonitorFactoryFactory({
|
|
115
117
|
assetName: base.name,
|
|
116
118
|
interval: config.feeMonitorInterval || feeMonitorInterval || DEFAULT_FEE_MONITOR_INTERVAL,
|
|
119
|
+
eip1559Enabled: feeData.eip1559Enabled, // this is not updated via remote config. Should it be?
|
|
117
120
|
})
|
|
118
121
|
|
|
119
122
|
const features = {
|
|
@@ -126,6 +129,7 @@ export const createAssetFactory = ({
|
|
|
126
129
|
nfts,
|
|
127
130
|
noHistory: monitorType === 'no-history',
|
|
128
131
|
signWithSigner: true,
|
|
132
|
+
signMessageWithSigner: true,
|
|
129
133
|
...(supportsStaking && { staking: {} }),
|
|
130
134
|
}
|
|
131
135
|
|
|
@@ -165,6 +169,17 @@ export const createAssetFactory = ({
|
|
|
165
169
|
const defaultAddressPath = 'm/0/0'
|
|
166
170
|
|
|
167
171
|
const sendTx = txSendFactory({ assetClientInterface })
|
|
172
|
+
|
|
173
|
+
const estimateL1DataFee = l1GasOracleAddress
|
|
174
|
+
? estimateL1DataFeeFactory({ l1GasOracleAddress, server })
|
|
175
|
+
: undefined
|
|
176
|
+
|
|
177
|
+
const originalGetFee = getFeeFactory({ gasLimit })
|
|
178
|
+
|
|
179
|
+
const getFee = l1GasOracleAddress
|
|
180
|
+
? getL1GetFeeFactory({ asset, originalGetFee })
|
|
181
|
+
: originalGetFee
|
|
182
|
+
|
|
168
183
|
const api = {
|
|
169
184
|
addressHasHistory,
|
|
170
185
|
broadcastTx: (...args) => server.sendRawTransaction(...args),
|
|
@@ -176,9 +191,10 @@ export const createAssetFactory = ({
|
|
|
176
191
|
defaultAddressPath,
|
|
177
192
|
features,
|
|
178
193
|
getBalances,
|
|
194
|
+
getBalanceForAddress: createGetBalanceForAddress({ asset, server }),
|
|
179
195
|
getConfirmationsNumber: () => confirmationNumber,
|
|
180
196
|
getDefaultAddressPath: () => defaultAddressPath,
|
|
181
|
-
getFee
|
|
197
|
+
getFee,
|
|
182
198
|
getFeeData: () => feeData,
|
|
183
199
|
getKeyIdentifier: createGetKeyIdentifier({ bip44, allowMetaMaskCompat }),
|
|
184
200
|
getSupportedPurposes: () => [44],
|
|
@@ -207,6 +223,8 @@ export const createAssetFactory = ({
|
|
|
207
223
|
keys,
|
|
208
224
|
address,
|
|
209
225
|
api,
|
|
226
|
+
estimateL1DataFee,
|
|
227
|
+
server,
|
|
210
228
|
...(erc20FuelBuffer && { erc20FuelBuffer }),
|
|
211
229
|
...(fuelThreshold && { fuelThreshold: asset.currency.defaultUnit(fuelThreshold) }),
|
|
212
230
|
getEffectiveGasPrice,
|
|
@@ -160,6 +160,21 @@ export default class ApiCoinNodesServer extends EventEmitter {
|
|
|
160
160
|
// for fee monitor
|
|
161
161
|
getGasPrice = this.gasPrice
|
|
162
162
|
|
|
163
|
+
async getLatestBlock() {
|
|
164
|
+
const request = this.buildRequest({
|
|
165
|
+
method: 'eth_getBlockByNumber',
|
|
166
|
+
params: ['latest', false],
|
|
167
|
+
})
|
|
168
|
+
return this.sendRequest(request)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async getBaseFeePerGas() {
|
|
172
|
+
const response = await this.getLatestBlock()
|
|
173
|
+
if (response.baseFeePerGas) {
|
|
174
|
+
return fromHexToString(response.baseFeePerGas)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
163
178
|
async estimateGas(...params) {
|
|
164
179
|
const request = this.estimateGasRequest(...params)
|
|
165
180
|
return this.sendRequest(request)
|
|
@@ -126,6 +126,17 @@ export function create(defaultURL, ensAssetName) {
|
|
|
126
126
|
return requestWithRetry('proxy', { method: 'eth_gasPrice' })
|
|
127
127
|
},
|
|
128
128
|
|
|
129
|
+
async getLatestBlock() {
|
|
130
|
+
return this.getBlockByNumber('latest')
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
async getBaseFeePerGas() {
|
|
134
|
+
const response = await this.getLatestBlock()
|
|
135
|
+
if (response.baseFeePerGas) {
|
|
136
|
+
return fromHexToString(response.baseFeePerGas)
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
|
|
129
140
|
async getTransactionCount(address, tag = 'latest') {
|
|
130
141
|
return requestWithRetry('proxy', { method: 'eth_getTransactionCount', address, tag })
|
|
131
142
|
},
|
|
@@ -366,6 +366,17 @@ export default class ClarityServer extends EventEmitter {
|
|
|
366
366
|
return this.sendRequest(request)
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
async getLatestBlock() {
|
|
370
|
+
return this.getBlockByNumber('latest')
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async getBaseFeePerGas() {
|
|
374
|
+
const response = await this.getLatestBlock()
|
|
375
|
+
if (response.baseFeePerGas) {
|
|
376
|
+
return fromHexToString(response.baseFeePerGas)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
369
380
|
async getBlockByHash(...params) {
|
|
370
381
|
const request = this.getBlockByHashRequest(...params)
|
|
371
382
|
return this.sendRequest(request)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FeeMonitor } from '@exodus/asset-lib'
|
|
2
2
|
import assert from 'minimalistic-assert'
|
|
3
3
|
import { getServerByName } from '../exodus-eth-server'
|
|
4
|
+
import { DEFAULT_FEE_MONITOR_INTERVAL } from '@exodus/ethereum-lib'
|
|
5
|
+
import { fromHexToString } from '../number-utils'
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Generic eth server based fee monitor.
|
|
@@ -16,24 +18,37 @@ import { getServerByName } from '../exodus-eth-server'
|
|
|
16
18
|
* }
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
|
-
export const serverBasedFeeMonitorFactoryFactory = ({
|
|
21
|
+
export const serverBasedFeeMonitorFactoryFactory = ({
|
|
22
|
+
assetName,
|
|
23
|
+
interval = DEFAULT_FEE_MONITOR_INTERVAL,
|
|
24
|
+
eip1559Enabled,
|
|
25
|
+
}) => {
|
|
20
26
|
assert(assetName, 'assetName is required')
|
|
21
|
-
|
|
22
27
|
// NOTE: Using getServerByName for simplicity now but
|
|
23
28
|
// remove getServerByName and convert server to a param instead.
|
|
24
29
|
// This is to avoid global references, static creation, remove the chain specific map and allow IOC
|
|
25
30
|
const server = getServerByName(assetName)
|
|
26
|
-
|
|
27
|
-
const FeeMonitorClass = class ServerBaseEthereumFeeMonitor extends
|
|
31
|
+
|
|
32
|
+
const FeeMonitorClass = class ServerBaseEthereumFeeMonitor extends FeeMonitor {
|
|
28
33
|
constructor({ updateFee }) {
|
|
29
34
|
assert(updateFee, 'updateFee is required')
|
|
30
35
|
super({
|
|
31
36
|
updateFee,
|
|
32
37
|
assetName,
|
|
33
|
-
getGasPrice,
|
|
34
38
|
interval,
|
|
35
39
|
})
|
|
36
40
|
}
|
|
41
|
+
|
|
42
|
+
async fetchFee() {
|
|
43
|
+
const gasPrice = fromHexToString(await server.getGasPrice())
|
|
44
|
+
|
|
45
|
+
const baseFeePerGas = eip1559Enabled ? `${await server.getBaseFeePerGas()} wei` : undefined
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
gasPrice: `${gasPrice} wei`,
|
|
49
|
+
baseFeePerGas,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
37
52
|
}
|
|
38
53
|
return (...args) => new FeeMonitorClass(...args)
|
|
39
54
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import assert from 'minimalistic-assert'
|
|
2
|
+
|
|
3
|
+
export const createGetBalanceForAddress = ({ asset, server }) => {
|
|
4
|
+
assert(asset, 'asset is required')
|
|
5
|
+
assert(server, 'server is required')
|
|
6
|
+
return async (address) => {
|
|
7
|
+
const balances = await server.getBalance(address)
|
|
8
|
+
return asset.currency.baseUnit(balances.confirmed.value)
|
|
9
|
+
}
|
|
10
|
+
}
|
package/src/get-balances.js
CHANGED
|
@@ -2,21 +2,6 @@ import { isRpcBalanceAsset } from '@exodus/ethereum-lib'
|
|
|
2
2
|
|
|
3
3
|
import { get } from 'lodash'
|
|
4
4
|
|
|
5
|
-
const fixBalance = ({ txLog, balance }) => {
|
|
6
|
-
for (const tx of txLog) {
|
|
7
|
-
// TODO: pending can only be less than a few minutes old, we can only search the latest txs to improve performance
|
|
8
|
-
if (tx.sent && tx.pending && !tx.error) {
|
|
9
|
-
// coinAmount is negative for sent tx
|
|
10
|
-
balance = balance.sub(tx.coinAmount.abs())
|
|
11
|
-
if (tx.coinAmount.unitType.equals(tx.feeAmount.unitType)) {
|
|
12
|
-
balance = balance.sub(tx.feeAmount)
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return balance
|
|
18
|
-
}
|
|
19
|
-
|
|
20
5
|
const getBalanceFromAccountState = ({ asset, accountState }) => {
|
|
21
6
|
const isBase = asset.name === asset.baseAsset.name
|
|
22
7
|
return get(
|
|
@@ -44,14 +29,33 @@ const getUnstaked = ({ accountState, asset }) => {
|
|
|
44
29
|
)
|
|
45
30
|
}
|
|
46
31
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
32
|
+
const getUnconfirmedSentBalanceFromTxLog = ({ asset, txLog }) => {
|
|
33
|
+
let result = asset.currency.ZERO
|
|
34
|
+
|
|
35
|
+
for (const tx of txLog) {
|
|
36
|
+
const isUnconfirmed = !tx.failed && tx.pending
|
|
37
|
+
|
|
38
|
+
if (isUnconfirmed && tx.sent) {
|
|
39
|
+
result = result.add(tx.coinAmount.abs())
|
|
40
|
+
if (tx.feeAmount && tx.coinAmount.unitType.equals(tx.feeAmount.unitType)) {
|
|
41
|
+
result = result.add(tx.feeAmount.abs())
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
51
45
|
|
|
52
|
-
|
|
46
|
+
return result
|
|
47
|
+
}
|
|
53
48
|
|
|
54
|
-
|
|
49
|
+
const getUnconfirmedReceivedBalanceFromTxLog = ({ asset, txLog }) => {
|
|
50
|
+
let result = asset.currency.ZERO
|
|
51
|
+
for (const tx of txLog) {
|
|
52
|
+
const isUnconfirmed = !tx.failed && tx.pending
|
|
53
|
+
if (isUnconfirmed && !tx.sent) {
|
|
54
|
+
result = result.add(tx.coinAmount.abs())
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return result
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
/**
|
|
@@ -63,13 +67,18 @@ function getBasicSpendable({ asset, accountState, txLog }) {
|
|
|
63
67
|
* @returns {{balance}|null} an object with the balance or null if the balance is unknown
|
|
64
68
|
*/
|
|
65
69
|
export const getBalances = ({ asset, txLog, accountState }) => {
|
|
66
|
-
const
|
|
70
|
+
const unconfirmedReceived = getUnconfirmedReceivedBalanceFromTxLog({ asset, txLog })
|
|
71
|
+
const unconfirmedSent = getUnconfirmedSentBalanceFromTxLog({ asset, txLog })
|
|
67
72
|
|
|
73
|
+
const balanceWithoutUnconfirmedSent = isRpcBalanceAsset(asset)
|
|
74
|
+
? getBalanceFromAccountState({ asset, accountState }).sub(unconfirmedSent)
|
|
75
|
+
: getBalanceFromTxLog({ txLog, asset })
|
|
76
|
+
|
|
77
|
+
const spendable = balanceWithoutUnconfirmedSent.sub(unconfirmedReceived)
|
|
68
78
|
const staked = getStaked({ asset, accountState })
|
|
69
79
|
const unstaking = getUnstaking({ asset, accountState })
|
|
70
80
|
const unstaked = getUnstaked({ asset, accountState })
|
|
71
|
-
|
|
72
|
-
const total = spendable.add(staked).add(unstaking).add(unstaked)
|
|
81
|
+
const total = balanceWithoutUnconfirmedSent.add(staked).add(unstaking).add(unstaked)
|
|
73
82
|
|
|
74
83
|
return {
|
|
75
84
|
// new
|
|
@@ -78,6 +87,8 @@ export const getBalances = ({ asset, txLog, accountState }) => {
|
|
|
78
87
|
staked,
|
|
79
88
|
unstaking,
|
|
80
89
|
unstaked,
|
|
90
|
+
unconfirmedReceived,
|
|
91
|
+
unconfirmedSent,
|
|
81
92
|
// legacy
|
|
82
93
|
balance: total,
|
|
83
94
|
spendableBalance: spendable,
|
|
@@ -3,19 +3,48 @@ import { createEthereumJsTx, createContract } from '@exodus/ethereum-lib'
|
|
|
3
3
|
import { getServerByName } from '../exodus-eth-server'
|
|
4
4
|
import { GAS_ORACLE_ADDRESS } from './addresses'
|
|
5
5
|
import { fromHexToBigInt } from '../number-utils'
|
|
6
|
+
import assert from 'minimalistic-assert'
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
+
export const estimateL1DataFeeFactory = ({ l1GasOracleAddress, server }) => {
|
|
9
|
+
assert(l1GasOracleAddress, 'l1GasOracleAddress is required')
|
|
10
|
+
assert(server, 'server is required')
|
|
11
|
+
const gasContract = createContract(l1GasOracleAddress, 'optimismGasOracle')
|
|
12
|
+
return async ({ unsignedTx }) => {
|
|
13
|
+
const ethjsTx = createEthereumJsTx(unsignedTx)
|
|
14
|
+
const serialized = ethjsTx.serialize()
|
|
15
|
+
const callData = gasContract.getL1Fee.build(serialized)
|
|
16
|
+
const buffer = Buffer.from(callData)
|
|
17
|
+
const data = ethUtil.bufferToHex(buffer)
|
|
18
|
+
const hex = await server.ethCall({ to: l1GasOracleAddress, data }, 'latest')
|
|
19
|
+
const l1DataFee = fromHexToBigInt(hex)
|
|
20
|
+
const padFee = l1DataFee / BigInt(4)
|
|
21
|
+
const maxL1DataFee = l1DataFee + padFee
|
|
22
|
+
return maxL1DataFee.toString()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const getL1GetFeeFactory = ({ asset, originalGetFee }) => {
|
|
27
|
+
return ({ feeOpts, ...args }) => {
|
|
28
|
+
const { fee, ...rest } = originalGetFee(args)
|
|
29
|
+
if (!feeOpts?.optimismL1DataFee) {
|
|
30
|
+
// hardcoded optimism name is for back compatiblity, it could be base!
|
|
31
|
+
// better to rename
|
|
32
|
+
return { fee, ...rest }
|
|
33
|
+
}
|
|
8
34
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const callData = gasContract.getL1Fee.build(serialized)
|
|
13
|
-
const buffer = Buffer.from(callData)
|
|
14
|
-
const data = ethUtil.bufferToHex(buffer)
|
|
15
|
-
const server = getServerByName('optimism')
|
|
16
|
-
const hex = await server.ethCall({ to: GAS_ORACLE_ADDRESS, data }, 'latest')
|
|
17
|
-
const l1DataFee = fromHexToBigInt(hex)
|
|
18
|
-
const padFee = l1DataFee / BigInt(4)
|
|
19
|
-
const maxL1DataFee = l1DataFee + padFee
|
|
20
|
-
return maxL1DataFee.toString()
|
|
35
|
+
const l1DataFee = asset.currency.baseUnit(feeOpts.optimismL1DataFee)
|
|
36
|
+
return { fee: fee.add(l1DataFee), ...rest }
|
|
37
|
+
}
|
|
21
38
|
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Back-compatibility
|
|
42
|
+
*
|
|
43
|
+
* @deprecated use eth-asset.estimateL1DataFee
|
|
44
|
+
* @param unsignedTx
|
|
45
|
+
* @returns {Promise<string>}
|
|
46
|
+
*/
|
|
47
|
+
export const estimateOptimismL1DataFee = estimateL1DataFeeFactory({
|
|
48
|
+
server: getServerByName('optimism'),
|
|
49
|
+
l1GasOracleAddress: GAS_ORACLE_ADDRESS,
|
|
50
|
+
})
|
package/src/tx-send/tx-send.js
CHANGED
|
@@ -34,6 +34,12 @@ const txSendFactory = ({ assetClientInterface }) => {
|
|
|
34
34
|
const assets = await assetClientInterface.getAssetsForNetwork({ baseAssetName: baseAsset.name })
|
|
35
35
|
let eip1559Enabled = baseAsset.name === 'ethereum' // TODO: temp override, clean up use of eip1559Enabled flag and default to always true
|
|
36
36
|
|
|
37
|
+
// Using a default zero value to not break code relying on the `tx.feeAmount` property.
|
|
38
|
+
// For example, some exchange providers don't supply this.
|
|
39
|
+
if (!feeAmount) {
|
|
40
|
+
feeAmount = asset.baseAsset.currency.ZERO
|
|
41
|
+
}
|
|
42
|
+
|
|
37
43
|
const fromAddress = await assetClientInterface.getReceiveAddress({
|
|
38
44
|
assetName: baseAsset.name,
|
|
39
45
|
walletAccount,
|