@exodus/ethereum-api 7.3.2 → 7.4.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": "7.3.2",
3
+ "version": "7.4.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": "06a32175d0e18e2fca6c74e55313b68ce0c2b402"
60
+ "gitHead": "ac617aef893ac9d6b1689ca12e7b6de68a258621"
61
61
  }
@@ -36,6 +36,8 @@ import { createStakingApi } from './staking-api'
36
36
  import { txSendFactory } from './tx-send'
37
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')
@@ -126,6 +128,7 @@ export const createAssetFactory = ({
126
128
  nfts,
127
129
  noHistory: monitorType === 'no-history',
128
130
  signWithSigner: true,
131
+ signMessageWithSigner: true,
129
132
  ...(supportsStaking && { staking: {} }),
130
133
  }
131
134
 
@@ -165,6 +168,17 @@ export const createAssetFactory = ({
165
168
  const defaultAddressPath = 'm/0/0'
166
169
 
167
170
  const sendTx = txSendFactory({ assetClientInterface })
171
+
172
+ const estimateL1DataFee = l1GasOracleAddress
173
+ ? estimateL1DataFeeFactory({ l1GasOracleAddress, server })
174
+ : undefined
175
+
176
+ const originalGetFee = getFeeFactory({ gasLimit })
177
+
178
+ const getFee = l1GasOracleAddress
179
+ ? getL1GetFeeFactory({ asset, originalGetFee })
180
+ : originalGetFee
181
+
168
182
  const api = {
169
183
  addressHasHistory,
170
184
  broadcastTx: (...args) => server.sendRawTransaction(...args),
@@ -176,9 +190,10 @@ export const createAssetFactory = ({
176
190
  defaultAddressPath,
177
191
  features,
178
192
  getBalances,
193
+ getBalanceForAddress: createGetBalanceForAddress({ asset, server }),
179
194
  getConfirmationsNumber: () => confirmationNumber,
180
195
  getDefaultAddressPath: () => defaultAddressPath,
181
- getFee: customGetFee || getFeeFactory({ gasLimit }),
196
+ getFee,
182
197
  getFeeData: () => feeData,
183
198
  getKeyIdentifier: createGetKeyIdentifier({ bip44, allowMetaMaskCompat }),
184
199
  getSupportedPurposes: () => [44],
@@ -207,6 +222,8 @@ export const createAssetFactory = ({
207
222
  keys,
208
223
  address,
209
224
  api,
225
+ estimateL1DataFee,
226
+ server,
210
227
  ...(erc20FuelBuffer && { erc20FuelBuffer }),
211
228
  ...(fuelThreshold && { fuelThreshold: asset.currency.defaultUnit(fuelThreshold) }),
212
229
  getEffectiveGasPrice,
@@ -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
+ }
@@ -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
- function getBasicSpendable({ asset, accountState, txLog }) {
48
- const balance = isRpcBalanceAsset(asset)
49
- ? getBalanceFromAccountState({ asset, accountState })
50
- : getBalanceFromTxLog({ txLog, asset })
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
- const shouldFixBalance = isRpcBalanceAsset(asset)
46
+ return result
47
+ }
53
48
 
54
- return shouldFixBalance ? fixBalance({ txLog, balance }) : balance
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 spendable = getBasicSpendable({ asset, accountState, txLog })
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 gasContract = createContract(GAS_ORACLE_ADDRESS, 'optimismGasOracle')
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
- export async function estimateOptimismL1DataFee({ unsignedTx }) {
10
- const ethjsTx = createEthereumJsTx(unsignedTx)
11
- const serialized = ethjsTx.serialize()
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
+ })