@exodus/ethereum-api 9.0.0-alpha.3 → 9.0.1

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 +2139 -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 +273 -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 +276 -103
  84. package/src/tx-log/clarity-truncated-history-monitor.js +130 -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,44 @@ 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,
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')
94
+ assert(defaultMonitorType, 'monitorType is required')
66
95
  assert(confirmationsNumber, 'confirmationsNumber is required')
67
96
 
68
97
  const base = assetsList.find((asset) => asset.name === asset.baseAssetName)
98
+
99
+ const feeData = providedFeeData || createFeeData({ currency: base.currency, feeDataConfig })
69
100
  assert(base, 'base is required')
70
101
 
71
102
  const chainId = base.chainId
@@ -73,32 +104,66 @@ export const createAssetFactory = ({
73
104
 
74
105
  const createGetKeyIdentifier = customCreateGetKeyIdentifier || defaultCreateGetKeyIdentifier
75
106
 
107
+ const defaultConfig = {
108
+ allowMetaMaskCompat: false,
109
+ supportsStaking: false,
110
+ supportsCustomFees: defaultSupportsCustomFees,
111
+ nfts: defaultNfts,
112
+ customTokens: defaultCustomTokens,
113
+ supportsTransactionPrivacy: defaultSupportsTransactionPrivacy,
114
+ transactionAssessment: defaultTransactionAssessment,
115
+ multicall3Address: defaultMulticall3Address,
116
+ }
76
117
  return (
77
118
  {
78
119
  assetClientInterface,
79
- config = {
80
- allowMetaMaskCompat: false,
81
- supportsStaking: false,
82
- },
120
+ config = Object.create(null),
83
121
  overrideCallback = ({ asset }) => asset,
84
122
  } = Object.create(null)
85
123
  ) => {
86
124
  const assets = connectAssetsList(assetsList)
87
125
 
88
- const { allowMetaMaskCompat, supportsStaking } = config
126
+ const configWithOverrides = { ...defaultConfig, ...config }
127
+
128
+ const {
129
+ allowMetaMaskCompat,
130
+ supportsStaking,
131
+ nfts,
132
+ customTokens,
133
+ supportsCustomFees,
134
+ useAbsoluteBalanceAndNonce: overrideUseAbsoluteBalanceAndNonce,
135
+ supportsTransactionPrivacy,
136
+ transactionAssessment,
137
+ multicall3Address,
138
+ } = configWithOverrides
89
139
 
90
140
  const asset = assets[base.name]
91
141
 
92
- const server = createEvmServer({ assetName: asset.name, serverUrl, monitorType })
142
+ const { monitorType, serverUrl, monitorInterval } = resolveMonitorSettings({
143
+ asset,
144
+ configWithOverrides,
145
+ defaultMonitorInterval,
146
+ defaultMonitorType,
147
+ defaultServerUrl,
148
+ defaultWsGatewayUri,
149
+ })
93
150
 
94
- const gasLimit = 21e3 // 21 KGas, enough only for sending ether to normal address
151
+ if (overrideUseAbsoluteBalanceAndNonce !== undefined) {
152
+ useAbsoluteBalanceAndNonce = overrideUseAbsoluteBalanceAndNonce
153
+ }
95
154
 
96
- const contractGasLimit = 1e6 // used when estimateGas fail
155
+ const server = createEvmServer({
156
+ assetName: asset.name,
157
+ serverUrl,
158
+ monitorType,
159
+ })
97
160
 
98
161
  const address = {
99
162
  validate: validateFactory({ chainId, useEip1191ChainIdChecksum }),
100
163
  hasChecksum,
101
164
  isContract: server.isContract,
165
+ displayAddress: (addr) =>
166
+ useEip1191ChainIdChecksum ? toChecksumAddress(addr, chainId) : toChecksumAddress(addr),
102
167
  }
103
168
 
104
169
  const bip44 = customBip44 || bip44Constants['ETH']
@@ -108,36 +173,88 @@ export const createAssetFactory = ({
108
173
  encodePublic: encodePublicFactory({ chainId, useEip1191ChainIdChecksum }),
109
174
  }
110
175
 
111
- const getBalances = getBalancesFactory({ monitorType })
176
+ // The Safe 4337 contract constants baked into the derivation are chainId 1 specific
177
+ const encodeMultisigContract =
178
+ chainId === 1
179
+ ? createEncodeMultisigContract({ chainId, encodePublic: keys.encodePublic })
180
+ : undefined
181
+
182
+ const getBalances = getBalancesFactory({
183
+ monitorType,
184
+ useAbsoluteBalance: useAbsoluteBalanceAndNonce,
185
+ rpcBalanceAssetNames,
186
+ })
187
+
188
+ const historicalBalanceSupported = monitorType !== 'no-history'
189
+ const getHistoricalBalance = getHistoricalBalanceFactory({
190
+ assetClientInterface,
191
+ getBalances,
192
+ })
193
+
194
+ const createRpcRequestAccumulator = createRpcRequestAccumulatorFactory({
195
+ multicall3Address,
196
+ })
112
197
 
113
198
  const { createToken, getTokens } = createTokenFactory(
114
- { address, bip44, keys, getBalances },
199
+ {
200
+ address,
201
+ bip44,
202
+ keys,
203
+ getBalances,
204
+ getHistoricalBalance,
205
+ historicalBalance: historicalBalanceSupported,
206
+ assetClientInterface,
207
+ server,
208
+ stakingConfiguration,
209
+ stakingDependencies,
210
+ createRpcRequestAccumulator,
211
+ },
115
212
  assets
116
213
  )
117
214
 
118
215
  const addressHasHistory = addressHasHistoryFactory({ server })
119
216
 
120
- const feeMonitor = monitorType === 'no-history'
217
+ const isWebSocket = monitorType !== 'no-history'
218
+
219
+ const resolvedFeeMonitorInterval =
220
+ config.feeMonitorInterval ||
221
+ feeMonitorInterval ||
222
+ (isWebSocket ? null : DEFAULT_FEE_MONITOR_INTERVAL) // null means do not start the timer
121
223
 
122
224
  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?
225
+ asset: base,
226
+ interval: resolvedFeeMonitorInterval,
227
+ aci: assetClientInterface,
126
228
  server,
127
229
  })
128
230
 
231
+ const { broadcastPrivateBundle, broadcastPrivateTx, sendPrivateTx, sendPrivateBundle } =
232
+ createTransactionPrivacyFactory({
233
+ assetClientInterface,
234
+ assetName: asset.name,
235
+ server,
236
+ supportsTransactionPrivacy,
237
+ })
238
+
129
239
  const features = {
130
240
  accountState: true,
131
241
  customTokens,
132
- feeMonitor,
242
+ family: ASSET_FAMILY.EVM,
243
+ feeMonitor: true,
133
244
  feesApi: true,
245
+ historicalBalance: historicalBalanceSupported,
134
246
  isMaxFeeAsset,
135
247
  isTestnet,
248
+ moveFunds: true,
249
+ multisig: Boolean(encodeMultisigContract),
136
250
  nfts,
137
251
  noHistory: monitorType === 'no-history',
138
252
  signWithSigner: true,
139
253
  signMessageWithSigner: true,
254
+ supportsCustomFees,
255
+ supportsTransactionPrivacy: Boolean(supportsTransactionPrivacy),
140
256
  ...(supportsStaking && { staking: {} }),
257
+ ...(delisted && { delisted }),
141
258
  }
142
259
 
143
260
  const stakingAssetNames = Object.keys(stakingConfiguration)
@@ -145,6 +262,8 @@ export const createAssetFactory = ({
145
262
  const extraData =
146
263
  stakingAssetNames.length > 0
147
264
  ? {
265
+ // Persist ownership independently of host annotations and balances.
266
+ stakingSnapshotOwners: {},
148
267
  staking: mapValues(
149
268
  stakingConfiguration,
150
269
  (assetStakinConfiguration) => assetStakinConfiguration.accountStateExtraData
@@ -152,76 +271,102 @@ export const createAssetFactory = ({
152
271
  }
153
272
  : undefined
154
273
 
274
+ const getBlackListStatus = createGetBlackListStatus({ server, address, blacklistChecks })
275
+
276
+ // eip7702Supported is the chain-level feature flag ([] when supported).
277
+ // The whitelist itself is populated from remote config through the
278
+ // history monitor's setServer.
279
+ const eip7702Whitelist = createEip7702Whitelist(eip7702Supported)
280
+
155
281
  const accountStateClass =
156
282
  CustomAccountState || createEthereumLikeAccountState({ asset: base, assets, extraData })
157
283
 
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
- }
284
+ const createHistoryMonitor = createHistoryMonitorFactory({
285
+ assetName: asset.name,
286
+ assetClientInterface,
287
+ monitorInterval,
288
+ monitorType,
289
+ server,
290
+ stakingConfiguration,
291
+ rpcBalanceAssetNames,
292
+ eip7702Whitelist,
293
+ getBlackListStatus,
294
+ })
190
295
 
191
296
  const defaultAddressPath = 'm/0/0'
192
297
 
193
298
  const createUnsignedTx = createUnsignedTxFactory({ chainId })
194
299
 
300
+ const createTx = createTxFactory({
301
+ chainId,
302
+ assetClientInterface,
303
+ useAbsoluteNonce: useAbsoluteBalanceAndNonce,
304
+ })
305
+
195
306
  const sendTx = txSendFactory({
196
307
  assetClientInterface,
197
- createUnsignedTx,
308
+ createTx,
198
309
  })
199
310
 
311
+ const getFeeAsync = async (...args) => {
312
+ const { unsignedTx, amount, ...rest } = await createTx(...args)
313
+ return rest
314
+ }
315
+
200
316
  const estimateL1DataFee = l1GasOracleAddress
201
317
  ? estimateL1DataFeeFactory({ l1GasOracleAddress, server })
202
318
  : undefined
203
319
 
204
- const originalGetFee = getFeeFactory({ gasLimit })
320
+ const originalGetFee = getFeeFactory()
205
321
 
206
322
  const getFee = l1GasOracleAddress
207
323
  ? getL1GetFeeFactory({ asset, originalGetFee })
208
324
  : originalGetFee
209
325
 
326
+ const { getNonce } = getNonceFactory({ assetClientInterface, useAbsoluteBalanceAndNonce })
327
+
328
+ const securityChecks = createSecurityChecks({ eip7702Whitelist })
329
+
330
+ const web3 = createWeb3API({ asset })
331
+
332
+ let checkTx
333
+ let sendValidations = []
334
+ if (transactionAssessment?.enabled) {
335
+ checkTx = createCheckTx({
336
+ makeApiCall: createAssessTransaction({ apiUrl: transactionAssessment.apiUrl }),
337
+ logger: createConsoleLogger('@exodus/ethereum-api:check-tx'),
338
+ })
339
+ sendValidations = createSendValidations({ assetClientInterface, checkTx })
340
+ }
341
+
342
+ const moveFunds = moveFundsFactory({
343
+ baseAssetName: asset.name,
344
+ assetClientInterface,
345
+ createTx,
346
+ server,
347
+ })
348
+
210
349
  const api = {
211
350
  addressHasHistory,
212
351
  broadcastTx: (...args) => server.sendRawTransaction(...args),
213
352
  createAccountState: () => accountStateClass,
353
+ securityChecks,
214
354
  createFeeMonitor,
215
355
  createHistoryMonitor,
216
356
  createToken,
217
- createUnsignedTx,
357
+ createTx,
358
+ createUnsignedTx, // @deprecated use createTx instead
359
+ customFees: createCustomFeesApi({ baseAsset: asset }),
218
360
  defaultAddressPath,
219
361
  features,
362
+ getActivityTxs,
220
363
  getBalances,
364
+ ...(features.historicalBalance && { getHistoricalBalance }),
221
365
  getBalanceForAddress: createGetBalanceForAddress({ asset, server }),
366
+ ...(getBlackListStatus && { getBlackListStatus }),
222
367
  getConfirmationsNumber: () => confirmationsNumber,
223
368
  getDefaultAddressPath: () => defaultAddressPath,
224
- getFeeAsync: getFeeAsyncFactory({ assetClientInterface, gasLimit, createUnsignedTx }),
369
+ getFeeAsync, // createTx alias, remove me when possible
225
370
  getFee,
226
371
  getFeeData: () => feeData,
227
372
  getKeyIdentifier: createGetKeyIdentifier({
@@ -230,38 +375,69 @@ export const createAssetFactory = ({
230
375
  assetName: asset.name,
231
376
  }),
232
377
  getSupportedPurposes: () => [44],
378
+ ...(sendValidations.length > 0 && { getSendValidations: () => sendValidations }),
233
379
  getTokens,
234
380
  hasFeature: (feature) => !!features[feature], // @deprecated use api.features instead
381
+ moveFunds,
382
+ privateKeyEncodingDefinition: { encoding: 'hex' },
235
383
  sendTx,
236
384
  signTx: ({ unsignedTx, privateKey, signer }) =>
237
385
  signer
238
386
  ? signUnsignedTxWithSigner(unsignedTx, signer)
239
387
  : signUnsignedTx(unsignedTx, privateKey),
240
- signHardware: signHardwareFactory({ baseAssetName: asset.name }),
388
+ signHardware: signHardwareFactory({
389
+ baseAssetName: asset.name,
390
+ getIsNft: ({ address }) => getIsNftContract({ server, address }),
391
+ }),
241
392
  signMessage: ({ message, privateKey, signer }) =>
242
393
  signer ? signMessageWithSigner({ message, signer }) : signMessage({ privateKey, message }),
243
- ...(supportsStaking && { staking: createStakingApi({ network: asset.name }) }),
394
+ ...(checkTx && { checkTx }),
395
+ ...(supportsStaking &&
396
+ stakingDependencies[asset.name] && {
397
+ staking: stakingApiFactory({
398
+ assetName: asset.name,
399
+ currency: asset.currency,
400
+ assetClientInterface,
401
+ server,
402
+ stakingConfiguration: stakingConfiguration[asset.name],
403
+ stakingDependencies: stakingDependencies[asset.name],
404
+ }),
405
+ }),
244
406
  validateAssetId: address.validate,
245
- web3: createWeb3API({ asset }),
407
+ web3,
246
408
  }
247
409
 
248
410
  const fullAsset = {
249
411
  ...asset,
250
- ...pick(config, ['accountReserve']),
251
- gasLimit,
252
- contractGasLimit,
253
412
  bip44,
254
413
  keys,
255
414
  address,
256
415
  api,
257
416
  chainId,
417
+ ...(encodeMultisigContract && { encodeMultisigContract }),
258
418
  monitorType,
259
419
  estimateL1DataFee,
420
+ forceGasLimitEstimation,
421
+ eip7623Supported,
422
+ // Stable array reference kept up to date with remote config by the
423
+ // history monitor's setServer.
424
+ eip7702Supported: eip7702Whitelist.get(),
425
+ getEIP7702Delegation: (addr) => getEIP7702Delegation({ address: addr, server }),
426
+ getNonce,
260
427
  server,
261
428
  ...(erc20FuelBuffer && { erc20FuelBuffer }),
262
429
  ...(fuelThreshold && { fuelThreshold: asset.currency.defaultUnit(fuelThreshold) }),
263
- getEffectiveGasPrice,
430
+ broadcastPrivateBundle,
431
+ broadcastPrivateTx,
432
+ createRpcRequestAccumulator,
433
+ sendPrivateTx,
434
+ sendPrivateBundle,
264
435
  }
265
- return overrideCallback({ asset: fullAsset })
436
+ return overrideCallback({
437
+ asset: fullAsset,
438
+ stakingConfiguration,
439
+ assetClientInterface,
440
+ monitorInterval,
441
+ })
266
442
  }
267
443
  }
@@ -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
+ }