@exodus/ethereum-api 9.0.0-alpha.3 → 9.0.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.
Files changed (143) hide show
  1. package/CHANGELOG.md +2121 -0
  2. package/LICENSE +7 -0
  3. package/README.md +124 -0
  4. package/package.json +34 -33
  5. package/src/address-has-history.js +6 -2
  6. package/src/allowance/constants.js +4 -5
  7. package/src/allowance/index.js +159 -40
  8. package/src/check-tx/create-assess-transaction.js +23 -0
  9. package/src/check-tx/create-check-tx.js +104 -0
  10. package/src/check-tx/index.js +1 -0
  11. package/src/create-asset-plugin-factory.js +115 -0
  12. package/src/create-asset-utils.js +520 -0
  13. package/src/create-asset.js +272 -97
  14. package/src/create-token-factory.js +47 -17
  15. package/src/custom-fees.js +57 -0
  16. package/src/eip7702-whitelist.js +62 -0
  17. package/src/ens/index.js +12 -17
  18. package/src/error-wrapper.js +383 -0
  19. package/src/eth-like-util.js +172 -33
  20. package/src/exodus-eth-server/api-coin-nodes.js +27 -196
  21. package/src/exodus-eth-server/api.js +23 -14
  22. package/src/exodus-eth-server/clarity-v2.js +255 -0
  23. package/src/exodus-eth-server/clarity.js +67 -265
  24. package/src/exodus-eth-server/errors.js +40 -0
  25. package/src/exodus-eth-server/eth-like-server.js +385 -0
  26. package/src/exodus-eth-server/fetch-json.js +48 -0
  27. package/src/exodus-eth-server/index.js +77 -0
  28. package/src/exodus-eth-server/utils.js +31 -0
  29. package/src/exodus-eth-server/ws-gateway.js +306 -0
  30. package/src/exodus-eth-server/ws.js +1 -2
  31. package/src/fee-data/EthLikeFeeData.js +81 -0
  32. package/src/fee-data/index.js +54 -0
  33. package/src/fee-utils.js +229 -0
  34. package/src/gas-estimation.js +183 -66
  35. package/src/get-balance-for-address.js +1 -1
  36. package/src/get-balances.js +285 -27
  37. package/src/get-fee.js +92 -56
  38. package/src/get-historical-balance.js +58 -0
  39. package/src/hooks/index.js +1 -1
  40. package/src/hooks/monitor.js +182 -43
  41. package/src/hooks/staking-freshness.js +58 -0
  42. package/src/index.js +118 -12
  43. package/src/move-funds.js +153 -0
  44. package/src/multicall3/index.js +212 -0
  45. package/src/multisig-address.js +146 -0
  46. package/src/nft-utils.js +60 -0
  47. package/src/number-utils.js +34 -2
  48. package/src/optimism-gas/index.js +13 -5
  49. package/src/send-validations.js +58 -0
  50. package/src/server-based-fee-monitor.js +12 -19
  51. package/src/simulate-tx/fetch-tx-preview.js +0 -2
  52. package/src/simulate-tx/index.js +2 -2
  53. package/src/simulate-tx/simulate-eth-tx.js +24 -11
  54. package/src/simulation/common.js +34 -0
  55. package/src/simulation/create-simulate-message.js +49 -0
  56. package/src/simulation/create-simulate-transactions.js +108 -0
  57. package/src/simulation/estimate-fee.js +14 -0
  58. package/src/simulation/estimate-simple-transfer.js +15 -0
  59. package/src/simulation/get-message-type.js +18 -0
  60. package/src/simulation/simulate-message-api.js +68 -0
  61. package/src/simulation/simulate-transactions-api.js +266 -0
  62. package/src/simulation/simulate-transactions.js +16 -0
  63. package/src/simulation/transactions.js +52 -0
  64. package/src/simulation/try-estimating-changes-locally.js +26 -0
  65. package/src/staking/api/index.js +43 -0
  66. package/src/staking/cached-read.js +33 -0
  67. package/src/staking/ethereum/api.js +166 -34
  68. package/src/staking/ethereum/deps.js +37 -0
  69. package/src/staking/ethereum/everstake.js +113 -0
  70. package/src/staking/ethereum/index.js +4 -3
  71. package/src/staking/ethereum/service.js +682 -181
  72. package/src/staking/ethereum/staking-utils.js +275 -4
  73. package/src/staking/fantom-staking.js +9 -9
  74. package/src/staking/index.js +4 -4
  75. package/src/staking/matic/addresses.js +2 -0
  76. package/src/staking/matic/api.js +69 -49
  77. package/src/staking/matic/index.js +3 -3
  78. package/src/staking/matic/matic-staking-utils.js +243 -5
  79. package/src/staking/matic/matic-staking.md +53 -0
  80. package/src/staking/staking-provider-client.js +1 -1
  81. package/src/staking/utils/index.js +39 -0
  82. package/src/tx-create.js +666 -0
  83. package/src/tx-log/clarity-monitor.js +273 -103
  84. package/src/tx-log/clarity-truncated-history-monitor.js +109 -0
  85. package/src/tx-log/clarity-truncated-history-monitor.md +219 -0
  86. package/src/tx-log/clarity-utils/absolute.js +96 -0
  87. package/src/tx-log/clarity-utils/get-log-items-from-server-tx.js +94 -31
  88. package/src/tx-log/clarity-utils/get-names-of-tokens-transferred-by-server-tx.js +3 -2
  89. package/src/tx-log/clarity-utils/index.js +9 -3
  90. package/src/tx-log/clarity-utils/is-spam-pending-tx.js +13 -0
  91. package/src/tx-log/clarity-utils/normalize-transactions-response.js +65 -0
  92. package/src/tx-log/ethereum-monitor.js +93 -47
  93. package/src/tx-log/ethereum-no-history-monitor.js +52 -41
  94. package/src/tx-log/get-optimistic-txlog-effects.js +379 -0
  95. package/src/tx-log/index.js +5 -3
  96. package/src/tx-log/monitor-utils/check-pending-transactions.js +14 -5
  97. package/src/tx-log/monitor-utils/exclude-unchanged-token-balances.js +1 -1
  98. package/src/tx-log/monitor-utils/extract-balance-from-clarity-account-info.js +108 -0
  99. package/src/tx-log/monitor-utils/get-balance-updates.js +44 -0
  100. package/src/tx-log/monitor-utils/get-batched-rpc-balances.js +47 -0
  101. package/src/tx-log/monitor-utils/get-current-blacklist-status.js +38 -0
  102. package/src/tx-log/monitor-utils/get-current-eip7702-delegation.js +79 -0
  103. package/src/tx-log/monitor-utils/get-derive-transactions-to-check.js +10 -5
  104. package/src/tx-log/monitor-utils/get-fee-amount.js +1 -1
  105. package/src/tx-log/monitor-utils/get-history-from-server.js +1 -1
  106. package/src/tx-log/monitor-utils/get-log-items-from-server-tx.js +75 -33
  107. package/src/tx-log/monitor-utils/get-names-of-tokens-transferred-by-server-tx.js +3 -2
  108. package/src/tx-log/monitor-utils/get-raw-transfer-amount.js +29 -0
  109. package/src/tx-log/monitor-utils/get-transfers-by-token-name.js +3 -2
  110. package/src/tx-log/monitor-utils/get-value-of-transfers.js +10 -12
  111. package/src/tx-log/monitor-utils/index.js +12 -7
  112. package/src/tx-log/monitor-utils/is-unverifiable-pending-tx-expired.js +19 -0
  113. package/src/tx-log/monitor-utils/verify-pending-tx-status-rpc.js +90 -0
  114. package/src/tx-log-staking-processor/asset-staking-tx-data.js +82 -24
  115. package/src/tx-log-staking-processor/index.js +28 -14
  116. package/src/tx-log-staking-processor/utils.js +21 -20
  117. package/src/tx-send/broadcast-error-handler.js +63 -0
  118. package/src/tx-send/index.js +1 -2
  119. package/src/tx-send/nonce-utils.js +97 -0
  120. package/src/tx-send/tx-send.js +94 -288
  121. package/src/tx-sign/index.js +17 -0
  122. package/src/tx-type/index.js +280 -0
  123. package/src/watch-tx.js +36 -0
  124. package/src/web3/createSimulateMessage.js +3 -2
  125. package/src/web3/createSimulateTransactions.js +11 -0
  126. package/src/web3/index.js +3 -1
  127. package/src/etherscan/account.js +0 -49
  128. package/src/etherscan/index.js +0 -28
  129. package/src/etherscan/logs.js +0 -16
  130. package/src/etherscan/proxy.js +0 -47
  131. package/src/etherscan/request.js +0 -26
  132. package/src/etherscan/ws.js +0 -88
  133. package/src/exodus-eth-server/server-factory.js +0 -28
  134. package/src/exodus-eth-server/server-map.js +0 -21
  135. package/src/get-fee-async.js +0 -97
  136. package/src/staking/matic/service.js +0 -437
  137. package/src/staking-api.js +0 -5
  138. package/src/tx-log/clarity-utils/get-derive-data-needed-for-tick.js +0 -12
  139. package/src/tx-log-staking-processor/get-asset-tx-amount.js +0 -26
  140. package/src/tx-send/get-fee-info.js +0 -41
  141. package/src/websocket/index.android.js +0 -2
  142. package/src/websocket/index.ios.js +0 -2
  143. package/src/websocket/index.js +0 -21
@@ -1,43 +1,57 @@
1
- import { connectAssetsList } from '@exodus/assets'
2
- import { pick } from '@exodus/basic-utils'
3
- import bip44Constants from '@exodus/bip44-constants/by-ticker'
4
- import { getBalancesFactory } 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 { createEvmServer } from './exodus-eth-server/server-factory'
10
-
1
+ import { mapValues } from '@exodus/basic-utils'
2
+ import { createConsoleLogger } from '@exodus/asset-lib'
3
+ import { ASSET_FAMILY, connectAssetsList } from '@exodus/assets'
4
+ import bip44Constants from '@exodus/bip44-constants/by-ticker.js'
11
5
  import {
12
6
  createEthereumLikeAccountState,
13
7
  createGetKeyIdentifier as defaultCreateGetKeyIdentifier,
8
+ createUnsignedTxFactory,
9
+ DEFAULT_FEE_MONITOR_INTERVAL,
14
10
  encodePrivate,
15
11
  encodePublicFactory,
12
+ getActivityTxs,
16
13
  hasChecksum,
17
- signUnsignedTx,
18
- signUnsignedTxWithSigner,
14
+ signHardwareFactory,
19
15
  signMessage,
20
16
  signMessageWithSigner,
21
- signHardwareFactory,
22
- DEFAULT_FEE_MONITOR_INTERVAL,
17
+ signUnsignedTx,
18
+ signUnsignedTxWithSigner,
23
19
  validateFactory,
24
- createUnsignedTxFactory,
25
20
  } from '@exodus/ethereum-lib'
21
+ import { toChecksumAddress } from '@exodus/ethereumjs/util'
26
22
  import assert from 'minimalistic-assert'
27
- import ms from 'ms'
28
-
29
- import { addressHasHistoryFactory } from './address-has-history'
30
- import { createTokenFactory } from './create-token-factory'
31
- import { getEffectiveGasPrice, getFeeFactory } from './get-fee'
32
- import { createEthereumHooks } from './hooks'
33
- import { createStakingApi } from './staking-api'
34
- import { txSendFactory } from './tx-send'
35
- import { serverBasedFeeMonitorFactoryFactory } from './server-based-fee-monitor'
36
- import { createGetBalanceForAddress } from './get-balance-for-address'
37
- import { estimateL1DataFeeFactory, getL1GetFeeFactory } from './optimism-gas'
38
- import { mapValues } from 'lodash'
39
- import { createWeb3API } from './web3'
40
- import getFeeAsyncFactory from './get-fee-async'
23
+
24
+ import { addressHasHistoryFactory } from './address-has-history.js'
25
+ import { createAssessTransaction } from './check-tx/create-assess-transaction.js'
26
+ import { createCheckTx } from './check-tx/index.js'
27
+ import {
28
+ createGetBlackListStatus,
29
+ createHistoryMonitorFactory,
30
+ createRpcRequestAccumulatorFactory,
31
+ createSecurityChecks,
32
+ createTransactionPrivacyFactory,
33
+ getNonceFactory,
34
+ resolveMonitorSettings,
35
+ } from './create-asset-utils.js'
36
+ import { createTokenFactory } from './create-token-factory.js'
37
+ import { createCustomFeesApi } from './custom-fees.js'
38
+ import { createEip7702Whitelist } from './eip7702-whitelist.js'
39
+ import { getEIP7702Delegation, getIsNftContract } from './eth-like-util.js'
40
+ import { createEvmServer } from './exodus-eth-server/index.js'
41
+ import { createFeeData } from './fee-data/index.js'
42
+ import { createGetBalanceForAddress } from './get-balance-for-address.js'
43
+ import { getBalancesFactory } from './get-balances.js'
44
+ import { getFeeFactory } from './get-fee.js'
45
+ import { getHistoricalBalanceFactory } from './get-historical-balance.js'
46
+ import { moveFundsFactory } from './move-funds.js'
47
+ import { createEncodeMultisigContract } from './multisig-address.js'
48
+ import { estimateL1DataFeeFactory, getL1GetFeeFactory } from './optimism-gas/index.js'
49
+ import { createSendValidations } from './send-validations.js'
50
+ import { serverBasedFeeMonitorFactoryFactory } from './server-based-fee-monitor.js'
51
+ import { stakingApiFactory } from './staking/api/index.js'
52
+ import { createTxFactory } from './tx-create.js'
53
+ import { txSendFactory } from './tx-send/index.js'
54
+ import { createWeb3API } from './web3/index.js'
41
55
 
42
56
  export const createAssetFactory = ({
43
57
  AccountState: CustomAccountState,
@@ -45,27 +59,43 @@ export const createAssetFactory = ({
45
59
  confirmationsNumber,
46
60
  customBip44,
47
61
  customCreateGetKeyIdentifier,
48
- customTokens = true,
62
+ customTokens: defaultCustomTokens = true,
49
63
  erc20FuelBuffer,
50
- feeData,
64
+ feeData: providedFeeData,
65
+ feeDataConfig,
51
66
  feeMonitorInterval,
52
67
  fuelThreshold,
53
68
  isMaxFeeAsset = false,
54
69
  isTestnet = false,
55
70
  l1GasOracleAddress, // l1 extra fee for base and optostakingConfiguration = {},
56
- monitorInterval,
57
- monitorType = 'magnifier',
58
- nfts = false,
59
- serverUrl,
60
- stakingConfiguration = {},
71
+ monitorInterval: defaultMonitorInterval,
72
+ monitorType: defaultMonitorType = 'magnifier',
73
+ multicall3Address: defaultMulticall3Address,
74
+ nfts: defaultNfts = false,
75
+ serverUrl: defaultServerUrl,
76
+ stakingConfiguration = Object.create(null),
77
+ stakingDependencies = Object.create(null),
61
78
  useEip1191ChainIdChecksum = false,
79
+ forceGasLimitEstimation = false,
80
+ rpcBalanceAssetNames = [],
81
+ blacklistChecks = [],
82
+ supportsCustomFees: defaultSupportsCustomFees = false,
83
+ useAbsoluteBalanceAndNonce = false,
84
+ delisted = false,
85
+ supportsTransactionPrivacy: defaultSupportsTransactionPrivacy,
86
+ wsGatewayUri: defaultWsGatewayUri,
87
+ eip7623Supported = false,
88
+ eip7702Supported,
89
+ transactionAssessment: defaultTransactionAssessment,
62
90
  }) => {
63
91
  assert(assetsList, 'assetsList is required')
64
- assert(feeData, 'feeData is required')
65
- assert(serverUrl, 'serverUrl is required')
92
+ assert(providedFeeData || feeDataConfig, 'feeData or feeDataConfig is required')
93
+ assert(defaultServerUrl, 'serverUrl is required')
66
94
  assert(confirmationsNumber, 'confirmationsNumber is required')
67
95
 
68
96
  const base = assetsList.find((asset) => asset.name === asset.baseAssetName)
97
+
98
+ const feeData = providedFeeData || createFeeData({ currency: base.currency, feeDataConfig })
69
99
  assert(base, 'base is required')
70
100
 
71
101
  const chainId = base.chainId
@@ -73,32 +103,66 @@ export const createAssetFactory = ({
73
103
 
74
104
  const createGetKeyIdentifier = customCreateGetKeyIdentifier || defaultCreateGetKeyIdentifier
75
105
 
106
+ const defaultConfig = {
107
+ allowMetaMaskCompat: false,
108
+ supportsStaking: false,
109
+ supportsCustomFees: defaultSupportsCustomFees,
110
+ nfts: defaultNfts,
111
+ customTokens: defaultCustomTokens,
112
+ supportsTransactionPrivacy: defaultSupportsTransactionPrivacy,
113
+ transactionAssessment: defaultTransactionAssessment,
114
+ multicall3Address: defaultMulticall3Address,
115
+ }
76
116
  return (
77
117
  {
78
118
  assetClientInterface,
79
- config = {
80
- allowMetaMaskCompat: false,
81
- supportsStaking: false,
82
- },
119
+ config = Object.create(null),
83
120
  overrideCallback = ({ asset }) => asset,
84
121
  } = Object.create(null)
85
122
  ) => {
86
123
  const assets = connectAssetsList(assetsList)
87
124
 
88
- const { allowMetaMaskCompat, supportsStaking } = config
125
+ const configWithOverrides = { ...defaultConfig, ...config }
126
+
127
+ const {
128
+ allowMetaMaskCompat,
129
+ supportsStaking,
130
+ nfts,
131
+ customTokens,
132
+ supportsCustomFees,
133
+ useAbsoluteBalanceAndNonce: overrideUseAbsoluteBalanceAndNonce,
134
+ supportsTransactionPrivacy,
135
+ transactionAssessment,
136
+ multicall3Address,
137
+ } = configWithOverrides
89
138
 
90
139
  const asset = assets[base.name]
91
140
 
92
- const server = createEvmServer({ assetName: asset.name, serverUrl, monitorType })
141
+ const { monitorType, serverUrl, monitorInterval } = resolveMonitorSettings({
142
+ asset,
143
+ configWithOverrides,
144
+ defaultMonitorInterval,
145
+ defaultMonitorType,
146
+ defaultServerUrl,
147
+ defaultWsGatewayUri,
148
+ })
93
149
 
94
- const gasLimit = 21e3 // 21 KGas, enough only for sending ether to normal address
150
+ if (overrideUseAbsoluteBalanceAndNonce !== undefined) {
151
+ useAbsoluteBalanceAndNonce = overrideUseAbsoluteBalanceAndNonce
152
+ }
95
153
 
96
- const contractGasLimit = 1e6 // used when estimateGas fail
154
+ const server = createEvmServer({
155
+ assetName: asset.name,
156
+ serverUrl,
157
+ monitorType,
158
+ })
97
159
 
98
160
  const address = {
99
161
  validate: validateFactory({ chainId, useEip1191ChainIdChecksum }),
100
162
  hasChecksum,
101
163
  isContract: server.isContract,
164
+ displayAddress: (addr) =>
165
+ useEip1191ChainIdChecksum ? toChecksumAddress(addr, chainId) : toChecksumAddress(addr),
102
166
  }
103
167
 
104
168
  const bip44 = customBip44 || bip44Constants['ETH']
@@ -108,36 +172,88 @@ export const createAssetFactory = ({
108
172
  encodePublic: encodePublicFactory({ chainId, useEip1191ChainIdChecksum }),
109
173
  }
110
174
 
111
- const getBalances = getBalancesFactory({ monitorType })
175
+ // The Safe 4337 contract constants baked into the derivation are chainId 1 specific
176
+ const encodeMultisigContract =
177
+ chainId === 1
178
+ ? createEncodeMultisigContract({ chainId, encodePublic: keys.encodePublic })
179
+ : undefined
180
+
181
+ const getBalances = getBalancesFactory({
182
+ monitorType,
183
+ useAbsoluteBalance: useAbsoluteBalanceAndNonce,
184
+ rpcBalanceAssetNames,
185
+ })
186
+
187
+ const historicalBalanceSupported = monitorType !== 'no-history'
188
+ const getHistoricalBalance = getHistoricalBalanceFactory({
189
+ assetClientInterface,
190
+ getBalances,
191
+ })
192
+
193
+ const createRpcRequestAccumulator = createRpcRequestAccumulatorFactory({
194
+ multicall3Address,
195
+ })
112
196
 
113
197
  const { createToken, getTokens } = createTokenFactory(
114
- { address, bip44, keys, getBalances },
198
+ {
199
+ address,
200
+ bip44,
201
+ keys,
202
+ getBalances,
203
+ getHistoricalBalance,
204
+ historicalBalance: historicalBalanceSupported,
205
+ assetClientInterface,
206
+ server,
207
+ stakingConfiguration,
208
+ stakingDependencies,
209
+ createRpcRequestAccumulator,
210
+ },
115
211
  assets
116
212
  )
117
213
 
118
214
  const addressHasHistory = addressHasHistoryFactory({ server })
119
215
 
120
- const feeMonitor = monitorType === 'no-history'
216
+ const isWebSocket = monitorType !== 'no-history'
217
+
218
+ const resolvedFeeMonitorInterval =
219
+ config.feeMonitorInterval ||
220
+ feeMonitorInterval ||
221
+ (isWebSocket ? null : DEFAULT_FEE_MONITOR_INTERVAL) // null means do not start the timer
121
222
 
122
223
  const createFeeMonitor = serverBasedFeeMonitorFactoryFactory({
123
- assetName: base.name,
124
- interval: config.feeMonitorInterval || feeMonitorInterval || DEFAULT_FEE_MONITOR_INTERVAL,
125
- eip1559Enabled: feeData.eip1559Enabled, // this is not updated via remote config. Should it be?
224
+ asset: base,
225
+ interval: resolvedFeeMonitorInterval,
226
+ aci: assetClientInterface,
126
227
  server,
127
228
  })
128
229
 
230
+ const { broadcastPrivateBundle, broadcastPrivateTx, sendPrivateTx, sendPrivateBundle } =
231
+ createTransactionPrivacyFactory({
232
+ assetClientInterface,
233
+ assetName: asset.name,
234
+ server,
235
+ supportsTransactionPrivacy,
236
+ })
237
+
129
238
  const features = {
130
239
  accountState: true,
131
240
  customTokens,
132
- feeMonitor,
241
+ family: ASSET_FAMILY.EVM,
242
+ feeMonitor: true,
133
243
  feesApi: true,
244
+ historicalBalance: historicalBalanceSupported,
134
245
  isMaxFeeAsset,
135
246
  isTestnet,
247
+ moveFunds: true,
248
+ multisig: Boolean(encodeMultisigContract),
136
249
  nfts,
137
250
  noHistory: monitorType === 'no-history',
138
251
  signWithSigner: true,
139
252
  signMessageWithSigner: true,
253
+ supportsCustomFees,
254
+ supportsTransactionPrivacy: Boolean(supportsTransactionPrivacy),
140
255
  ...(supportsStaking && { staking: {} }),
256
+ ...(delisted && { delisted }),
141
257
  }
142
258
 
143
259
  const stakingAssetNames = Object.keys(stakingConfiguration)
@@ -145,6 +261,8 @@ export const createAssetFactory = ({
145
261
  const extraData =
146
262
  stakingAssetNames.length > 0
147
263
  ? {
264
+ // Persist ownership independently of host annotations and balances.
265
+ stakingSnapshotOwners: {},
148
266
  staking: mapValues(
149
267
  stakingConfiguration,
150
268
  (assetStakinConfiguration) => assetStakinConfiguration.accountStateExtraData
@@ -152,76 +270,102 @@ export const createAssetFactory = ({
152
270
  }
153
271
  : undefined
154
272
 
273
+ const getBlackListStatus = createGetBlackListStatus({ server, address, blacklistChecks })
274
+
275
+ // eip7702Supported is the chain-level feature flag ([] when supported).
276
+ // The whitelist itself is populated from remote config through the
277
+ // history monitor's setServer.
278
+ const eip7702Whitelist = createEip7702Whitelist(eip7702Supported)
279
+
155
280
  const accountStateClass =
156
281
  CustomAccountState || createEthereumLikeAccountState({ asset: base, assets, extraData })
157
282
 
158
- const createHistoryMonitor = (args) => {
159
- let monitor
160
- switch (monitorType) {
161
- case 'clarity':
162
- monitor = new ClarityMonitor({ interval: ms(monitorInterval || '5m'), server, ...args })
163
- break
164
- case 'no-history':
165
- monitor = new EthereumNoHistoryMonitor({
166
- interval: ms(monitorInterval || '15s'),
167
- server,
168
- ...args,
169
- })
170
- break
171
- case 'magnifier':
172
- monitor = new EthereumMonitor({ interval: ms(monitorInterval || '15s'), server, ...args })
173
- break
174
- default:
175
- throw new Error(`Monitor type ${monitorType} of evm asset ${asset.name} is unknown`)
176
- }
177
-
178
- if (stakingAssetNames.length > 0) {
179
- const afterTickHook = createEthereumHooks({
180
- assetClientInterface,
181
- assetName: asset.name,
182
- stakingAssetNames,
183
- server,
184
- })['after-tick']
185
- monitor.addHook('after-tick', afterTickHook)
186
- }
187
-
188
- return monitor
189
- }
283
+ const createHistoryMonitor = createHistoryMonitorFactory({
284
+ assetName: asset.name,
285
+ assetClientInterface,
286
+ monitorInterval,
287
+ monitorType,
288
+ server,
289
+ stakingConfiguration,
290
+ rpcBalanceAssetNames,
291
+ eip7702Whitelist,
292
+ getBlackListStatus,
293
+ })
190
294
 
191
295
  const defaultAddressPath = 'm/0/0'
192
296
 
193
297
  const createUnsignedTx = createUnsignedTxFactory({ chainId })
194
298
 
299
+ const createTx = createTxFactory({
300
+ chainId,
301
+ assetClientInterface,
302
+ useAbsoluteNonce: useAbsoluteBalanceAndNonce,
303
+ })
304
+
195
305
  const sendTx = txSendFactory({
196
306
  assetClientInterface,
197
- createUnsignedTx,
307
+ createTx,
198
308
  })
199
309
 
310
+ const getFeeAsync = async (...args) => {
311
+ const { unsignedTx, amount, ...rest } = await createTx(...args)
312
+ return rest
313
+ }
314
+
200
315
  const estimateL1DataFee = l1GasOracleAddress
201
316
  ? estimateL1DataFeeFactory({ l1GasOracleAddress, server })
202
317
  : undefined
203
318
 
204
- const originalGetFee = getFeeFactory({ gasLimit })
319
+ const originalGetFee = getFeeFactory()
205
320
 
206
321
  const getFee = l1GasOracleAddress
207
322
  ? getL1GetFeeFactory({ asset, originalGetFee })
208
323
  : originalGetFee
209
324
 
325
+ const { getNonce } = getNonceFactory({ assetClientInterface, useAbsoluteBalanceAndNonce })
326
+
327
+ const securityChecks = createSecurityChecks({ eip7702Whitelist })
328
+
329
+ const web3 = createWeb3API({ asset })
330
+
331
+ let checkTx
332
+ let sendValidations = []
333
+ if (transactionAssessment?.enabled) {
334
+ checkTx = createCheckTx({
335
+ makeApiCall: createAssessTransaction({ apiUrl: transactionAssessment.apiUrl }),
336
+ logger: createConsoleLogger('@exodus/ethereum-api:check-tx'),
337
+ })
338
+ sendValidations = createSendValidations({ assetClientInterface, checkTx })
339
+ }
340
+
341
+ const moveFunds = moveFundsFactory({
342
+ baseAssetName: asset.name,
343
+ assetClientInterface,
344
+ createTx,
345
+ server,
346
+ })
347
+
210
348
  const api = {
211
349
  addressHasHistory,
212
350
  broadcastTx: (...args) => server.sendRawTransaction(...args),
213
351
  createAccountState: () => accountStateClass,
352
+ securityChecks,
214
353
  createFeeMonitor,
215
354
  createHistoryMonitor,
216
355
  createToken,
217
- createUnsignedTx,
356
+ createTx,
357
+ createUnsignedTx, // @deprecated use createTx instead
358
+ customFees: createCustomFeesApi({ baseAsset: asset }),
218
359
  defaultAddressPath,
219
360
  features,
361
+ getActivityTxs,
220
362
  getBalances,
363
+ ...(features.historicalBalance && { getHistoricalBalance }),
221
364
  getBalanceForAddress: createGetBalanceForAddress({ asset, server }),
365
+ ...(getBlackListStatus && { getBlackListStatus }),
222
366
  getConfirmationsNumber: () => confirmationsNumber,
223
367
  getDefaultAddressPath: () => defaultAddressPath,
224
- getFeeAsync: getFeeAsyncFactory({ assetClientInterface, gasLimit, createUnsignedTx }),
368
+ getFeeAsync, // createTx alias, remove me when possible
225
369
  getFee,
226
370
  getFeeData: () => feeData,
227
371
  getKeyIdentifier: createGetKeyIdentifier({
@@ -230,38 +374,69 @@ export const createAssetFactory = ({
230
374
  assetName: asset.name,
231
375
  }),
232
376
  getSupportedPurposes: () => [44],
377
+ ...(sendValidations.length > 0 && { getSendValidations: () => sendValidations }),
233
378
  getTokens,
234
379
  hasFeature: (feature) => !!features[feature], // @deprecated use api.features instead
380
+ moveFunds,
381
+ privateKeyEncodingDefinition: { encoding: 'hex' },
235
382
  sendTx,
236
383
  signTx: ({ unsignedTx, privateKey, signer }) =>
237
384
  signer
238
385
  ? signUnsignedTxWithSigner(unsignedTx, signer)
239
386
  : signUnsignedTx(unsignedTx, privateKey),
240
- signHardware: signHardwareFactory({ baseAssetName: asset.name }),
387
+ signHardware: signHardwareFactory({
388
+ baseAssetName: asset.name,
389
+ getIsNft: ({ address }) => getIsNftContract({ server, address }),
390
+ }),
241
391
  signMessage: ({ message, privateKey, signer }) =>
242
392
  signer ? signMessageWithSigner({ message, signer }) : signMessage({ privateKey, message }),
243
- ...(supportsStaking && { staking: createStakingApi({ network: asset.name }) }),
393
+ ...(checkTx && { checkTx }),
394
+ ...(supportsStaking &&
395
+ stakingDependencies[asset.name] && {
396
+ staking: stakingApiFactory({
397
+ assetName: asset.name,
398
+ currency: asset.currency,
399
+ assetClientInterface,
400
+ server,
401
+ stakingConfiguration: stakingConfiguration[asset.name],
402
+ stakingDependencies: stakingDependencies[asset.name],
403
+ }),
404
+ }),
244
405
  validateAssetId: address.validate,
245
- web3: createWeb3API({ asset }),
406
+ web3,
246
407
  }
247
408
 
248
409
  const fullAsset = {
249
410
  ...asset,
250
- ...pick(config, ['accountReserve']),
251
- gasLimit,
252
- contractGasLimit,
253
411
  bip44,
254
412
  keys,
255
413
  address,
256
414
  api,
257
415
  chainId,
416
+ ...(encodeMultisigContract && { encodeMultisigContract }),
258
417
  monitorType,
259
418
  estimateL1DataFee,
419
+ forceGasLimitEstimation,
420
+ eip7623Supported,
421
+ // Stable array reference kept up to date with remote config by the
422
+ // history monitor's setServer.
423
+ eip7702Supported: eip7702Whitelist.get(),
424
+ getEIP7702Delegation: (addr) => getEIP7702Delegation({ address: addr, server }),
425
+ getNonce,
260
426
  server,
261
427
  ...(erc20FuelBuffer && { erc20FuelBuffer }),
262
428
  ...(fuelThreshold && { fuelThreshold: asset.currency.defaultUnit(fuelThreshold) }),
263
- getEffectiveGasPrice,
429
+ broadcastPrivateBundle,
430
+ broadcastPrivateTx,
431
+ createRpcRequestAccumulator,
432
+ sendPrivateTx,
433
+ sendPrivateBundle,
264
434
  }
265
- return overrideCallback({ asset: fullAsset })
435
+ return overrideCallback({
436
+ asset: fullAsset,
437
+ stakingConfiguration,
438
+ assetClientInterface,
439
+ monitorInterval,
440
+ })
266
441
  }
267
442
  }
@@ -1,10 +1,16 @@
1
- import { isPolygonClaimUndelegate, isPolygonDelegate, isPolygonUndelegate } from './staking/matic'
1
+ import { ASSET_FAMILY } from '@exodus/assets'
2
2
  import { createContract } from '@exodus/ethereum-lib'
3
-
4
- import { createStakingApi } from './staking-api'
5
3
  import assert from 'minimalistic-assert'
6
4
 
5
+ import { stakingApiFactory } from './staking/api/index.js'
6
+ import {
7
+ isPolygonClaimUndelegate,
8
+ isPolygonDelegate,
9
+ isPolygonUndelegate,
10
+ } from './staking/matic/index.js'
11
+
7
12
  const defaultTokenFeatures = {
13
+ family: ASSET_FAMILY.EVM,
8
14
  isMaxFeeAsset: true,
9
15
  }
10
16
 
@@ -29,31 +35,55 @@ const getPolygonActivityTxs = ({ txs }) =>
29
35
  const getActivityTxs = ({ txs }) => txs.filter((tx) => !smallbalanceTx(tx))
30
36
 
31
37
  const getCreateBaseToken =
32
- ({ getBalances, ...props }) =>
38
+ ({
39
+ getBalances,
40
+ getHistoricalBalance,
41
+ historicalBalance,
42
+ assetClientInterface,
43
+ server,
44
+ stakingConfiguration = Object.create(null),
45
+ stakingDependencies = Object.create(null),
46
+ createRpcRequestAccumulator,
47
+ ...props
48
+ }) =>
33
49
  ({ name, contract, features, ...tokenDef }) => {
34
50
  assert(getBalances, 'getBalances is required')
35
51
  const tokenSpecificFeatures = name === 'polygon' ? { staking: {} } : {}
36
- const tokenSpecificApiFunctions =
37
- name === 'polygon' ? { staking: createStakingApi({ network: name }) } : {}
38
52
 
39
53
  const tokenFeatures = { ...defaultTokenFeatures, ...features, ...tokenSpecificFeatures }
40
- return {
54
+ const baseApi = {
55
+ getActivityTxs: name === 'polygon' ? getPolygonActivityTxs : getActivityTxs,
56
+ features: tokenFeatures,
57
+ hasFeature: (feature) => !!tokenFeatures[feature], // @deprecated use api.features instead
58
+ getBalances,
59
+ ...(historicalBalance && { getHistoricalBalance }),
60
+ getTxLogFilter: name === 'polygon' ? getPolygonTxLogFilter : getTxLogFilter,
61
+ }
62
+
63
+ const tokenAsset = {
41
64
  ...tokenDef,
42
65
  addresses: contract,
43
- assetId: contract.current,
66
+ assetId: contract.current.toLowerCase(),
44
67
  contract: createContract(contract.current, name),
45
68
  gasLimit: 120_000,
46
69
  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
70
+ api: baseApi,
71
+ ...props,
56
72
  }
73
+
74
+ if (name === 'polygon' && stakingDependencies[name]) {
75
+ baseApi.staking = stakingApiFactory({
76
+ assetName: tokenAsset.name,
77
+ currency: tokenAsset.currency,
78
+ assetClientInterface,
79
+ server,
80
+ stakingConfiguration: stakingConfiguration[name],
81
+ stakingDependencies: stakingDependencies[name],
82
+ createRpcRequestAccumulator,
83
+ })
84
+ }
85
+
86
+ return tokenAsset
57
87
  }
58
88
 
59
89
  export const createTokenFactory = (props, assets) => {
@@ -0,0 +1,57 @@
1
+ import NumberUnit from '@exodus/currency'
2
+
3
+ import { resolveGasPrice } from './fee-utils.js'
4
+
5
+ const calculateMinCustomFee = ({ feeData }) => {
6
+ const { eip1559Enabled, baseFeePerGas, gasPriceMinimumRate } = feeData
7
+
8
+ // On `eip1559Enabled` networks, the `baseFeePerGas` is
9
+ // the minimum a transaction should be sent using, else
10
+ // get rejected by the RPC.
11
+ if (eip1559Enabled && baseFeePerGas) return baseFeePerGas.mul(Math.max(gasPriceMinimumRate, 1))
12
+
13
+ // On legacy networks, senders are permitted to send
14
+ // transactions below the `baseFeePerGas` (since these
15
+ // would not be rejected by the RPC - in fact, it was
16
+ // even possible to mine transactions with a `gasPrice`
17
+ // of `0`!).
18
+ return resolveGasPrice({ feeData }).mul(gasPriceMinimumRate)
19
+ }
20
+
21
+ const calculateRecommendedCustomFee = ({ min, feeData }) => {
22
+ const recommended = resolveGasPrice({ feeData })
23
+ // The `recommended` fee must be at least the `min`.
24
+ return recommended.gt(min) ? recommended : min
25
+ }
26
+
27
+ const calculateMaxCustomFee = ({ feeData, recommended }) => {
28
+ const { gasPriceMinimumRate, gasPriceMaximumRate } = feeData
29
+
30
+ const maxGasPrice = resolveGasPrice({ feeData }).mul(
31
+ Math.max(gasPriceMaximumRate, gasPriceMinimumRate)
32
+ )
33
+
34
+ // When calculating the `max` custom fee, the returned fee
35
+ // must be at least the `recommended` price.
36
+ return recommended.gt(maxGasPrice) ? recommended : maxGasPrice
37
+ }
38
+
39
+ export const createCustomFeesApi = ({ baseAsset }) => {
40
+ const Gwei = baseAsset.currency.units.Gwei
41
+ return {
42
+ getRecommendedMinMaxFeeUnitPrices: ({ feeData }) => {
43
+ const min = calculateMinCustomFee({ feeData })
44
+ const recommended = calculateRecommendedCustomFee({ feeData, min })
45
+ const max = calculateMaxCustomFee({ feeData, recommended })
46
+ return {
47
+ recommended: recommended.toNumber(Gwei),
48
+ min: min.toNumber(Gwei),
49
+ max: max.toNumber(Gwei),
50
+ }
51
+ },
52
+ unit: 'gwei/gas',
53
+ feeUnitPriceToNumber: (feeNumberUnit) => feeNumberUnit.toNumber(Gwei),
54
+ numberToFeeUnitPrice: (value) => NumberUnit.create(value, Gwei),
55
+ isEnabled: ({ feeData }) => Boolean(feeData.rbfEnabled),
56
+ }
57
+ }