@exodus/ethereum-api 7.2.1 → 7.3.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.2.1",
3
+ "version": "7.3.0",
4
4
  "description": "Ethereum Api",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -20,12 +20,17 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@exodus/asset-lib": "^4.1.0",
23
+ "@exodus/assets": "^9.1.1",
24
+ "@exodus/basic-utils": "^2.1.0",
25
+ "@exodus/bip44-constants": "^195.0.0",
23
26
  "@exodus/crypto": "^1.0.0-rc.0",
24
27
  "@exodus/currency": "^2.1.3",
25
28
  "@exodus/ethereum-lib": "^4.2.7",
26
29
  "@exodus/ethereum-meta": "^1.2.0",
30
+ "@exodus/ethereumholesky-meta": "^1.0.1",
27
31
  "@exodus/ethereumjs-util": "^7.1.0-exodus.7",
28
- "@exodus/fetch": "^1.3.0-beta.4",
32
+ "@exodus/fetch": "^1.3.0",
33
+ "@exodus/key-utils": "^3.1.0",
29
34
  "@exodus/models": "^11.0.0",
30
35
  "@exodus/simple-retry": "^0.0.6",
31
36
  "@exodus/solidity-contract": "^1.1.3",
@@ -46,11 +51,11 @@
46
51
  "@exodus/bsc-meta": "^1.1.2",
47
52
  "@exodus/ethereumarbone-meta": "^1.1.5",
48
53
  "@exodus/ethereumgoerli-meta": "^1.0.3",
54
+ "@exodus/ethereumsepolia-meta": "^1.0.1",
49
55
  "@exodus/fantommainnet-meta": "^1.0.5",
50
56
  "@exodus/rootstock-meta": "^1.0.5",
51
- "bignumber.js": "9.0.1",
52
57
  "cross-fetch": "^3.1.5",
53
58
  "delay": "4.0.1"
54
59
  },
55
- "gitHead": "4e7cc9a7bf33f63f1b957005a75163a9f08f39f6"
60
+ "gitHead": "fc66c4756ed4022ae0889ca063adc0c3a17cbfd3"
56
61
  }
@@ -0,0 +1,6 @@
1
+ export const addressHasHistoryFactory =
2
+ ({ server }) =>
3
+ async (address) => {
4
+ const history = await server.getHistoryV2(address, { index: 0, limit: 1 })
5
+ return history.length > 0
6
+ }
@@ -0,0 +1,216 @@
1
+ import { connectAssetsList } from '@exodus/assets'
2
+ import { pick } from '@exodus/basic-utils'
3
+ import bip44Constants from '@exodus/bip44-constants/by-ticker'
4
+ import { getBalances } from './get-balances'
5
+ import { ClarityMonitor } from './tx-log/clarity-monitor'
6
+ import { EthereumNoHistoryMonitor } from './tx-log/ethereum-no-history-monitor'
7
+ import { EthereumMonitor } from './tx-log/ethereum-monitor'
8
+
9
+ import { getServer } from './exodus-eth-server'
10
+
11
+ import {
12
+ CONFIRMATIONS_NUMBER,
13
+ createEthereumLikeAccountState,
14
+ createGetKeyIdentifier as defaultCreateGetKeyIdentifier,
15
+ createUnsignedTx,
16
+ encodePrivate,
17
+ encodePublic,
18
+ hasChecksum,
19
+ parseUnsignedTx,
20
+ signUnsignedTx,
21
+ signUnsignedTxWithSigner,
22
+ validate,
23
+ signMessage,
24
+ signHardwareFactory,
25
+ DEFAULT_FEE_MONITOR_INTERVAL,
26
+ } from '@exodus/ethereum-lib'
27
+ import { ETHEREUM_LIKE_MONITOR_TYPES } from '@exodus/ethereum-lib/src/constants'
28
+ import assert from 'minimalistic-assert'
29
+ import ms from 'ms'
30
+
31
+ import { addressHasHistoryFactory } from './address-has-history'
32
+ import { createTokenFactory } from './create-token-factory'
33
+ import { getEffectiveGasPrice, getFeeFactory } from './get-fee'
34
+ import { createEthereumHooks } from './hooks'
35
+ import { createStakingApi } from './staking-api'
36
+ import { txSendFactory } from './tx-send'
37
+ import { signMessageWithSigner } from '@exodus/ethereum-lib/src/sign-message'
38
+ import { serverBasedFeeMonitorFactoryFactory } from './fee-monitor'
39
+
40
+ export const createAssetFactory = ({
41
+ assetsList,
42
+ feeData,
43
+ AccountState: CustomAccountState,
44
+ monitorInterval,
45
+ nfts = false,
46
+ getFee: customGetFee,
47
+ useEip1191ChainIdChecksum = false,
48
+ customTokens = true,
49
+ isTestnet = false,
50
+ isMaxFeeAsset = false,
51
+ erc20FuelBuffer,
52
+ fuelThreshold,
53
+ customBip44,
54
+ customCreateGetKeyIdentifier,
55
+ feeMonitorInterval,
56
+ }) => {
57
+ assert(assetsList, 'assetsList is required')
58
+ assert(feeData, 'feeData is required')
59
+
60
+ const base = assetsList.find((asset) => asset.name === asset.baseAssetName)
61
+ assert(base, 'base is required')
62
+
63
+ const createGetKeyIdentifier = customCreateGetKeyIdentifier || defaultCreateGetKeyIdentifier
64
+
65
+ return (
66
+ {
67
+ assetClientInterface,
68
+ config = {
69
+ allowMetaMaskCompat: false,
70
+ supportsStaking: false,
71
+ },
72
+ overrideCallback = ({ asset }) => asset,
73
+ } = Object.create(null)
74
+ ) => {
75
+ const assets = connectAssetsList(assetsList)
76
+
77
+ const { allowMetaMaskCompat, supportsStaking } = config
78
+
79
+ const asset = assets[base.name]
80
+
81
+ const server = getServer(asset)
82
+
83
+ const gasLimit = 21e3 // 21 KGas, enough only for sending ether to normal address
84
+
85
+ const contractGasLimit = 1e6 // used when estimateGas fail
86
+
87
+ const address = {
88
+ validate: useEip1191ChainIdChecksum
89
+ ? (address) => validate(address, { baseAssetName: asset.name })
90
+ : validate,
91
+ hasChecksum,
92
+ isContract: server.isContract,
93
+ }
94
+
95
+ const bip44 = customBip44 || bip44Constants['ETH']
96
+
97
+ const keys = {
98
+ encodePrivate,
99
+ encodePublic: useEip1191ChainIdChecksum
100
+ ? (pubKey) => encodePublic(pubKey, { baseAssetName: asset.name })
101
+ : encodePublic,
102
+ }
103
+
104
+ const { createToken, getTokens } = createTokenFactory({ address, bip44, keys }, assets)
105
+
106
+ const addressHasHistory = addressHasHistoryFactory({ server })
107
+
108
+ const monitorType = ETHEREUM_LIKE_MONITOR_TYPES[asset.name]
109
+
110
+ assert(monitorType, `ETHEREUM_LIKE_MONITOR_TYPES for asset ${asset.name} is not defined`)
111
+
112
+ const feeMonitor = monitorType === 'no-history'
113
+
114
+ const createFeeMonitor = serverBasedFeeMonitorFactoryFactory({
115
+ assetName: base.name,
116
+ interval: config.feeMonitorInterval || feeMonitorInterval || DEFAULT_FEE_MONITOR_INTERVAL,
117
+ })
118
+
119
+ const features = {
120
+ accountState: true,
121
+ customTokens,
122
+ feeMonitor,
123
+ feesApi: true,
124
+ isMaxFeeAsset,
125
+ isTestnet,
126
+ nfts,
127
+ noHistory: monitorType === 'no-history',
128
+ signWithSigner: true,
129
+ ...(supportsStaking && { staking: {} }),
130
+ }
131
+
132
+ const confirmationNumber = CONFIRMATIONS_NUMBER[asset.name] || 1
133
+
134
+ const accountStateClass =
135
+ CustomAccountState || createEthereumLikeAccountState({ asset: base, assets })
136
+
137
+ const createHistoryMonitor = (args) => {
138
+ let monitor
139
+ switch (monitorType) {
140
+ case 'clarity':
141
+ monitor = new ClarityMonitor({ interval: ms(monitorInterval || '5m'), server, ...args })
142
+ break
143
+ case 'no-history':
144
+ monitor = new EthereumNoHistoryMonitor({
145
+ interval: ms(monitorInterval || '15s'),
146
+ server,
147
+ ...args,
148
+ })
149
+ break
150
+ case 'magnifier':
151
+ monitor = new EthereumMonitor({ interval: ms(monitorInterval || '15s'), server, ...args })
152
+ break
153
+ default:
154
+ throw new Error(`Monitor type ${monitorType} of evm asset ${asset.name} is unknown`)
155
+ }
156
+
157
+ if (supportsStaking) {
158
+ const afterTickHook = createEthereumHooks({ assetClientInterface })['after-tick']
159
+ monitor.addHook('after-tick', afterTickHook)
160
+ }
161
+
162
+ return monitor
163
+ }
164
+
165
+ const defaultAddressPath = 'm/0/0'
166
+
167
+ const sendTx = txSendFactory({ assetClientInterface })
168
+ const api = {
169
+ addressHasHistory,
170
+ broadcastTx: (...args) => server.sendRawTransaction(...args),
171
+ createAccountState: () => accountStateClass,
172
+ createFeeMonitor,
173
+ createHistoryMonitor,
174
+ createToken,
175
+ createUnsignedTx,
176
+ defaultAddressPath,
177
+ features,
178
+ getBalances,
179
+ getConfirmationsNumber: () => confirmationNumber,
180
+ getDefaultAddressPath: () => defaultAddressPath,
181
+ getFee: customGetFee || getFeeFactory({ gasLimit }),
182
+ getFeeData: () => feeData,
183
+ getKeyIdentifier: createGetKeyIdentifier({ bip44, allowMetaMaskCompat }),
184
+ getSupportedPurposes: () => [44],
185
+ getTokens,
186
+ hasFeature: (feature) => !!features[feature], // @deprecated use api.features instead
187
+ parseUnsignedTx: (unsignedTx, { asset }) => parseUnsignedTx(asset, unsignedTx),
188
+ sendTx,
189
+ signTx: ({ unsignedTx, privateKey, signer }) =>
190
+ signer
191
+ ? signUnsignedTxWithSigner(unsignedTx, signer)
192
+ : signUnsignedTx(unsignedTx, privateKey),
193
+ signUnsignedTx,
194
+ signHardware: signHardwareFactory({ baseAssetName: asset.name }),
195
+ signMessage: ({ message, privateKey, signer }) =>
196
+ signer ? signMessageWithSigner({ message, signer }) : signMessage({ privateKey, message }),
197
+ ...(supportsStaking && { staking: createStakingApi({ network: asset.name }) }),
198
+ validateAssetId: address.validate,
199
+ }
200
+
201
+ const fullAsset = {
202
+ ...asset,
203
+ ...pick(config, ['accountReserve']),
204
+ gasLimit,
205
+ contractGasLimit,
206
+ bip44,
207
+ keys,
208
+ address,
209
+ api,
210
+ ...(erc20FuelBuffer && { erc20FuelBuffer }),
211
+ ...(fuelThreshold && { fuelThreshold: asset.currency.defaultUnit(fuelThreshold) }),
212
+ getEffectiveGasPrice,
213
+ }
214
+ return overrideCallback({ asset: fullAsset })
215
+ }
216
+ }
@@ -0,0 +1,72 @@
1
+ import { isPolygonClaimUndelegate, isPolygonDelegate, isPolygonUndelegate } from './staking/matic'
2
+ import { createContract } from '@exodus/ethereum-lib'
3
+
4
+ import { getBalances } from './get-balances'
5
+
6
+ import { createStakingApi } from './staking-api'
7
+
8
+ const defaultTokenFeatures = {
9
+ isMaxFeeAsset: true,
10
+ }
11
+
12
+ // Hide on both deposit and withdraw sides
13
+ const smallbalanceTx = (tx) => Math.abs(tx.coinAmount.toBaseNumber()) === 0
14
+
15
+ const getTxLogFilter = () => (tx) => !smallbalanceTx(tx)
16
+
17
+ const getPolygonTxLogFilter = () => (tx) =>
18
+ isPolygonDelegate(tx) ||
19
+ isPolygonClaimUndelegate(tx) ||
20
+ isPolygonUndelegate(tx) ||
21
+ getTxLogFilter()(tx)
22
+ const getPolygonActivityTxs = ({ txs }) =>
23
+ txs.filter(
24
+ (tx) =>
25
+ isPolygonDelegate(tx) ||
26
+ isPolygonClaimUndelegate(tx) ||
27
+ isPolygonUndelegate(tx) ||
28
+ !smallbalanceTx(tx)
29
+ )
30
+ const getActivityTxs = ({ txs }) => txs.filter((tx) => !smallbalanceTx(tx))
31
+
32
+ const getCreateBaseToken =
33
+ (props) =>
34
+ ({ name, contract, features, ...tokenDef }) => {
35
+ const tokenSpecificFeatures = name === 'polygon' ? { staking: {} } : {}
36
+ const tokenSpecificApiFunctions =
37
+ name === 'polygon' ? { staking: createStakingApi({ network: name }) } : {}
38
+
39
+ const tokenFeatures = { ...defaultTokenFeatures, ...features, ...tokenSpecificFeatures }
40
+ return {
41
+ ...tokenDef,
42
+ addresses: contract,
43
+ assetId: contract.current,
44
+ contract: createContract(contract.current, name),
45
+ gasLimit: 120_000,
46
+ name,
47
+ api: {
48
+ getActivityTxs: name === 'polygon' ? getPolygonActivityTxs : getActivityTxs,
49
+ features: tokenFeatures,
50
+ hasFeature: (feature) => !!tokenFeatures[feature], // @deprecated use api.features instead
51
+ getBalances,
52
+ getTxLogFilter: name === 'polygon' ? getPolygonTxLogFilter : getTxLogFilter,
53
+ ...tokenSpecificApiFunctions,
54
+ },
55
+ ...props, // override props above, add new props
56
+ }
57
+ }
58
+
59
+ export const createTokenFactory = (props, assets) => {
60
+ const createBaseToken = getCreateBaseToken(props)
61
+ const createCustomToken = ({ assetId, assetName, ...rest }) =>
62
+ createBaseToken({ ...rest, name: assetName, contract: { current: assetId } })
63
+ const createToken = (tokenDef) =>
64
+ tokenDef.isBuiltIn ? createBaseToken(tokenDef) : createCustomToken(tokenDef)
65
+ return {
66
+ createToken,
67
+ getTokens: () =>
68
+ Object.values(assets)
69
+ .filter((asset) => asset.name !== asset.baseAsset.name) // eslint-disable-next-line unicorn/no-array-callback-reference -- TODO: Fix this the next time the file is edited.
70
+ .map(createToken),
71
+ }
72
+ }
@@ -3,7 +3,7 @@ import { normalizeTxId, isEthereumLikeAsset, isEthereumLikeToken, ABI } from '@e
3
3
  import { memoizeLruCache } from '@exodus/asset-lib'
4
4
  import SolidityContract from '@exodus/solidity-contract'
5
5
  import { getServerByName, getServer } from './exodus-eth-server'
6
- import BN from 'bn.js'
6
+ import { fromHexToString } from './number-utils'
7
7
 
8
8
  export async function isContract(baseAssetName, address) {
9
9
  return getServerByName(baseAssetName).isContract(address)
@@ -43,8 +43,7 @@ export async function getBalance({ asset, address }) {
43
43
  export async function getBalanceProxied({ asset, address, tag = 'latest' }) {
44
44
  if (!isEthereumLikeAsset(asset)) throw new Error(`unsupported asset ${asset.name}`)
45
45
  const result = await getServer(asset).getBalanceProxied(address)
46
- const hex = result.startsWith('0x') ? result.slice(2) : result
47
- return new BN(hex, 'hex').toString()
46
+ return fromHexToString(result)
48
47
  }
49
48
 
50
49
  // Only ETH-like assets with token support
@@ -1,9 +1,9 @@
1
- import BN from 'bn.js'
2
1
  import { bufferToHex } from '@exodus/ethereumjs-util'
3
2
  import SolidityContract from '@exodus/solidity-contract'
4
3
  import EventEmitter from 'events'
5
4
  import { isEmpty } from 'lodash'
6
5
  import { fetch } from '@exodus/fetch'
6
+ import { fromHexToString } from '../number-utils'
7
7
 
8
8
  export default class ApiCoinNodesServer extends EventEmitter {
9
9
  constructor({ uri }) {
@@ -135,8 +135,7 @@ export default class ApiCoinNodesServer extends EventEmitter {
135
135
  async balanceOf(address, tokenAddress, tag = 'latest') {
136
136
  const request = this.balanceOfRequest(address, tokenAddress, tag)
137
137
  const result = await this.sendRequest(request)
138
- const hex = result.startsWith('0x') ? result.slice(2) : result
139
- const balance = new BN(hex, 'hex').toString()
138
+ const balance = fromHexToString(result)
140
139
  return {
141
140
  confirmed: {
142
141
  [tokenAddress]: balance,
@@ -1,11 +1,11 @@
1
1
  import ms from 'ms'
2
- import BN from 'bn.js'
3
2
  import createWebSocket from './ws'
4
3
  import fetchival from '@exodus/fetch/experimental/fetchival'
5
4
  import { retry } from '@exodus/simple-retry'
6
5
  import SolidityContract from '@exodus/solidity-contract'
7
6
  import { bufferToHex } from '@exodus/ethereumjs-util'
8
7
  import { randomUUID } from '@exodus/crypto/randomUUID'
8
+ import { fromHexToString } from '../number-utils'
9
9
 
10
10
  const RETRY_DELAYS = ['10s']
11
11
 
@@ -88,8 +88,7 @@ export function create(defaultURL, ensAssetName) {
88
88
  tag,
89
89
  }
90
90
  const result = await retry(this.ethCall, { delayTimesMs: RETRY_DELAYS })(data)
91
- const hex = result.startsWith('0x') ? result.slice(2) : result
92
- const balance = new BN(hex, 'hex').toString()
91
+ const balance = fromHexToString(result)
93
92
  return {
94
93
  confirmed: {
95
94
  [tokenAddress]: balance,
@@ -1,8 +1,8 @@
1
- import BN from 'bn.js'
2
1
  import { bufferToHex } from '@exodus/ethereumjs-util'
3
2
  import SolidityContract from '@exodus/solidity-contract'
4
3
  import EventEmitter from 'events'
5
4
  import io from 'socket.io-client'
5
+ import { fromHexToString } from '../number-utils'
6
6
 
7
7
  export default class ClarityServer extends EventEmitter {
8
8
  constructor({ baseAssetName, uri }) {
@@ -51,7 +51,10 @@ export default class ClarityServer extends EventEmitter {
51
51
  }
52
52
 
53
53
  createSocket(namespace) {
54
- return io(`${this.uri}${namespace}`, { transports: ['websocket', 'polling'] })
54
+ return io(`${this.uri}${namespace}`, {
55
+ transports: ['websocket', 'polling'],
56
+ extraHeaders: { 'User-Agent': 'exodus' },
57
+ })
55
58
  }
56
59
 
57
60
  disconnectTransactions(address) {
@@ -296,8 +299,7 @@ export default class ClarityServer extends EventEmitter {
296
299
  async balanceOf(address, tokenAddress, tag = 'latest') {
297
300
  const request = this.balanceOfRequest(address, tokenAddress, tag)
298
301
  const result = await this.sendRequest(request)
299
- const hex = result.startsWith('0x') ? result.slice(2) : result
300
- const balance = new BN(hex, 'hex').toString()
302
+ const balance = fromHexToString(result)
301
303
  return {
302
304
  confirmed: {
303
305
  [tokenAddress]: balance,
@@ -1,4 +1,4 @@
1
- import { getEthereumBalances, isRpcBalanceAsset } from '@exodus/ethereum-lib'
1
+ import { isRpcBalanceAsset } from '@exodus/ethereum-lib'
2
2
 
3
3
  import { get } from 'lodash'
4
4
 
@@ -30,22 +30,27 @@ const getBalanceFromTxLog = ({ txLog, asset }) => {
30
30
  return txLog.size > 0 ? txLog.getMutations().slice(-1)[0].balance : asset.currency.ZERO
31
31
  }
32
32
 
33
- const getBalance = ({ asset, accountState, txLog }) => {
33
+ const getStaked = ({ accountState, asset }) => {
34
+ return get(accountState, ['staking', asset.name, 'delegatedBalance']) || asset.currency.ZERO
35
+ }
36
+
37
+ const getUnstaking = ({ accountState, asset }) => {
38
+ return get(accountState, ['staking', asset.name, 'undelegatedBalance']) || asset.currency.ZERO
39
+ }
40
+
41
+ const getUnstaked = ({ accountState, asset }) => {
42
+ return (
43
+ get(accountState, ['staking', asset.name, 'unclaimedUndelegatedBalance']) || asset.currency.ZERO
44
+ )
45
+ }
46
+
47
+ function getBasicSpendable({ asset, accountState, txLog }) {
34
48
  const balance = isRpcBalanceAsset(asset)
35
49
  ? getBalanceFromAccountState({ asset, accountState })
36
50
  : getBalanceFromTxLog({ txLog, asset })
37
51
 
38
52
  const shouldFixBalance = isRpcBalanceAsset(asset)
39
53
 
40
- if (['ethereum', 'ethereumgoerli', 'ethereumholesky', 'ethereumsepolia'].includes(asset.name)) {
41
- const { balance: ethereumBalance } = getEthereumBalances({
42
- asset,
43
- liquidBalance: balance,
44
- accountState,
45
- })
46
- return ethereumBalance
47
- }
48
-
49
54
  return shouldFixBalance ? fixBalance({ txLog, balance }) : balance
50
55
  }
51
56
 
@@ -58,6 +63,23 @@ const getBalance = ({ asset, accountState, txLog }) => {
58
63
  * @returns {{balance}|null} an object with the balance or null if the balance is unknown
59
64
  */
60
65
  export const getBalances = ({ asset, txLog, accountState }) => {
61
- const balance = getBalance({ asset, accountState, txLog })
62
- return { balance }
66
+ const spendable = getBasicSpendable({ asset, accountState, txLog })
67
+
68
+ const staked = getStaked({ asset, accountState })
69
+ const unstaking = getUnstaking({ asset, accountState })
70
+ const unstaked = getUnstaked({ asset, accountState })
71
+
72
+ const total = spendable.add(staked).add(unstaking).add(unstaked)
73
+
74
+ return {
75
+ // new
76
+ spendable,
77
+ total,
78
+ staked,
79
+ unstaking,
80
+ unstaked,
81
+ // legacy
82
+ balance: total,
83
+ spendableBalance: spendable,
84
+ }
63
85
  }
package/src/get-fee.js ADDED
@@ -0,0 +1,97 @@
1
+ import { calculateBumpedGasPrice, calculateExtraEth, isEthereumLike } from '@exodus/ethereum-lib'
2
+
3
+ // Move to meta?
4
+ const taxes = {
5
+ paxgold: 0.0002,
6
+ }
7
+
8
+ const getGasPriceMultiplier = ({ asset, feeData, isExchange, isSendAll }) => {
9
+ // exchanges quotes expire, do not risk having a stuck tx
10
+ if (isExchange) return 1
11
+
12
+ // if eip1559 enabled, do not risk not leaving enough ETH to cover base fee (applies only for native asset)
13
+ // (gasPrice difference will be reimbursed anyway: users do not overpay)
14
+ if (isSendAll && isEthereumLike(asset) && feeData.eip1559Enabled) return 1
15
+
16
+ // do not risk having a stuck tx if we're not able to accelerate it
17
+ if (!feeData.rbfEnabled) return 1
18
+
19
+ return feeData.gasPriceEconomicalRate
20
+ }
21
+
22
+ const getExtraFeeData = ({ asset, amount }) => {
23
+ const tax = taxes[asset.name]
24
+ if (!amount || !tax || amount.isZero) {
25
+ return {}
26
+ }
27
+
28
+ const extraFee = amount.mul(tax)
29
+ return {
30
+ type: 'tax',
31
+ tax,
32
+ extraFee,
33
+ }
34
+ }
35
+
36
+ export const getFeeFactory =
37
+ ({ gasLimit: defaultGasLimit }) =>
38
+ ({
39
+ asset,
40
+ feeData,
41
+ customFee,
42
+ gasLimit = defaultGasLimit,
43
+ isExchange,
44
+ isSendAll,
45
+ amount,
46
+ calculateEffectiveFee, // BE
47
+ customFee: customGasPrice, // BE
48
+ }) => {
49
+ const { gasPrice, eip1559Enabled, baseFeePerGas, tipGasPrice } = feeData
50
+ const gasPriceMultiplier = getGasPriceMultiplier({
51
+ asset,
52
+ feeData,
53
+ isExchange,
54
+ isSendAll,
55
+ })
56
+
57
+ const extraFeeData = getExtraFeeData({ asset, amount })
58
+ if (calculateEffectiveFee && eip1559Enabled) {
59
+ const maxFeePerGas = customGasPrice || gasPrice
60
+ // effective_gas_price = min(base_fee_per_gas + tip_gas_price, max_fee_per_gas)
61
+ const feePerGas = baseFeePerGas.add(tipGasPrice)
62
+ const effectiveGasPrice = feePerGas.lt(maxFeePerGas) ? feePerGas : maxFeePerGas
63
+
64
+ return { fee: effectiveGasPrice.mul(gasLimit), extraFeeData }
65
+ }
66
+
67
+ const fee = (customFee || gasPrice.mul(gasPriceMultiplier)).mul(gasLimit || asset.gasLimit)
68
+ return { fee, extraFeeData }
69
+ }
70
+
71
+ // Used in BE
72
+ export const getEffectiveGasPrice = ({ feeData }) => {
73
+ const { baseFeePerGas, tipGasPrice, gasPrice: maxFeePerGas, eip1559Enabled } = feeData
74
+
75
+ if (!eip1559Enabled) {
76
+ return maxFeePerGas
77
+ }
78
+
79
+ const gasPrice = baseFeePerGas.add(tipGasPrice)
80
+ return gasPrice.lt(maxFeePerGas) ? gasPrice : maxFeePerGas
81
+ }
82
+
83
+ // Used in Mobile
84
+ export const getExtraFeeForBump = ({ tx, feeData, balance, unconfirmedBalance }) => {
85
+ if (!balance || !unconfirmedBalance) return null
86
+ const { gasPrice: currentGasPrice, eip1559Enabled } = feeData
87
+ const { bumpedGasPrice } = calculateBumpedGasPrice({
88
+ baseAsset: 'ethereum',
89
+ tx,
90
+ currentGasPrice,
91
+ eip1559Enabled,
92
+ })
93
+ return calculateExtraEth({
94
+ fee: bumpedGasPrice.mul(tx.data.gasLimit),
95
+ balance: balance.sub(unconfirmedBalance),
96
+ })
97
+ }
@@ -0,0 +1 @@
1
+ export { createEthereumHooks } from './monitor'
@@ -0,0 +1,88 @@
1
+ import { getEthereumStakingInfo, getPolygonStakingInfo } from '../staking'
2
+ import { isEmpty } from 'lodash'
3
+
4
+ import processTxLog from '../tx-log-staking-processor'
5
+
6
+ export const createEthereumHooks = ({ assetClientInterface }) => {
7
+ const afterTickHook = async ({ walletAccount }) => {
8
+ // args passed from monitor tick method ({ monitor, error })
9
+ const { ethereum, polygon } = await assetClientInterface.getAssetsForNetwork({
10
+ baseAssetName: 'ethereum',
11
+ })
12
+
13
+ const assetName = ethereum.name
14
+
15
+ const userAddress = await assetClientInterface.getReceiveAddress({
16
+ assetName,
17
+ walletAccount,
18
+ useCache: true,
19
+ })
20
+
21
+ const ethereumStakingInfo = await getEthereumStakingInfo({
22
+ address: userAddress.toString(),
23
+ asset: ethereum,
24
+ })
25
+
26
+ const polygonStakingInfo = await getPolygonStakingInfo({
27
+ address: userAddress.toString(),
28
+ asset: polygon,
29
+ })
30
+
31
+ const stakingInfo = {
32
+ staking: {
33
+ ethereum: ethereumStakingInfo,
34
+ polygon: polygonStakingInfo,
35
+ },
36
+ }
37
+
38
+ const batch = assetClientInterface.createOperationsBatch()
39
+ assetClientInterface.updateAccountStateBatch({
40
+ assetName: ethereum.name,
41
+ walletAccount,
42
+ newData: stakingInfo,
43
+ batch,
44
+ })
45
+
46
+ const processTxLogsPromises = []
47
+
48
+ const holeskyAssets = await assetClientInterface.getAssetsForNetwork({
49
+ baseAssetName: 'ethereumholesky',
50
+ })
51
+
52
+ if (!isEmpty(holeskyAssets)) {
53
+ const { ethereumholesky } = holeskyAssets
54
+ // eslint-disable-next-line no-console
55
+ console.log('ethereum-hooks updating holesky state')
56
+
57
+ const ethereumHoleskyInfo = await getEthereumStakingInfo({
58
+ address: userAddress.toString(),
59
+ asset: ethereumholesky,
60
+ })
61
+
62
+ assetClientInterface.updateAccountStateBatch({
63
+ assetName: ethereumholesky.name,
64
+ walletAccount,
65
+ newData: {
66
+ staking: {
67
+ ethereumholesky: ethereumHoleskyInfo,
68
+ },
69
+ },
70
+ batch,
71
+ })
72
+ processTxLogsPromises.push(
73
+ processTxLog({ asset: ethereumholesky, assetClientInterface, walletAccount, batch })
74
+ )
75
+ }
76
+
77
+ processTxLogsPromises.push(
78
+ processTxLog({ asset: polygon, assetClientInterface, walletAccount, batch }),
79
+ processTxLog({ asset: ethereum, assetClientInterface, walletAccount, batch })
80
+ )
81
+ await Promise.all(processTxLogsPromises)
82
+ await assetClientInterface.executeOperationsBatch(batch)
83
+ }
84
+
85
+ return {
86
+ 'after-tick': afterTickHook,
87
+ }
88
+ }