@exodus/ethereum-api 8.76.6 → 8.76.7

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 (35) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +2 -3
  3. package/src/create-asset-plugin-factory.js +1 -0
  4. package/src/create-asset-utils.js +39 -35
  5. package/src/create-asset.js +21 -14
  6. package/src/exodus-eth-server/api-coin-nodes.js +11 -84
  7. package/src/exodus-eth-server/clarity-v2.js +30 -51
  8. package/src/exodus-eth-server/clarity.js +2 -115
  9. package/src/exodus-eth-server/errors.js +5 -1
  10. package/src/exodus-eth-server/eth-like-server-base.js +123 -0
  11. package/src/exodus-eth-server/fetch-json.js +48 -0
  12. package/src/gas-estimation.js +19 -4
  13. package/src/get-balances.js +14 -0
  14. package/src/index.js +1 -0
  15. package/src/multicall3/index.js +169 -0
  16. package/src/simulation/common.js +34 -0
  17. package/src/simulation/create-simulate-message.js +49 -0
  18. package/src/simulation/create-simulate-transactions.js +106 -0
  19. package/src/simulation/estimate-fee.js +14 -0
  20. package/src/simulation/estimate-simple-transfer.js +15 -0
  21. package/src/simulation/get-message-type.js +18 -0
  22. package/src/simulation/simulate-message-api.js +68 -0
  23. package/src/simulation/simulate-transactions-api.js +265 -0
  24. package/src/simulation/simulate-transactions.js +16 -0
  25. package/src/simulation/transactions.js +52 -0
  26. package/src/simulation/try-estimating-changes-locally.js +26 -0
  27. package/src/staking/ethereum/staking-utils.js +3 -1
  28. package/src/staking/matic/matic-staking-utils.js +3 -1
  29. package/src/tx-log/clarity-truncated-history-monitor.js +34 -0
  30. package/src/tx-log/ethereum-no-history-monitor.js +2 -23
  31. package/src/tx-log/monitor-utils/get-batched-rpc-balances.js +28 -0
  32. package/src/tx-send/broadcast-error-handler.js +7 -2
  33. package/src/tx-send/tx-send.js +1 -0
  34. package/src/web3/createSimulateMessage.js +2 -1
  35. package/src/web3/createSimulateTransactions.js +3 -9
@@ -0,0 +1,68 @@
1
+ import { makeSimulationAPICall } from '@exodus/web3-utils'
2
+
3
+ import { BLOWFISH_EVM_CHAINS } from './common.js'
4
+ import { MessageTypeEnum } from './get-message-type.js'
5
+
6
+ const convertToAPIPayload = ({ address, message, url }) => {
7
+ const restParameters = {
8
+ metadata: { origin: url.href },
9
+ userAccount: address,
10
+ }
11
+
12
+ if (message.messageType === MessageTypeEnum.TypedData) {
13
+ return {
14
+ message: {
15
+ kind: 'SIGN_TYPED_DATA',
16
+ data: JSON.parse(message.message),
17
+ },
18
+ ...restParameters,
19
+ }
20
+ }
21
+
22
+ return {
23
+ message: {
24
+ kind: 'SIGN_MESSAGE',
25
+ rawMessage: message.message,
26
+ },
27
+ ...restParameters,
28
+ }
29
+ }
30
+
31
+ export const handleActionResponse = ({ simulationResult, response }) => {
32
+ if (['BLOCK', 'WARN'].includes(response.action)) {
33
+ // eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only
34
+ simulationResult.action = response.action
35
+ }
36
+ }
37
+
38
+ export const simulateMessageApi = async ({
39
+ address,
40
+ apiEndpoint,
41
+ message,
42
+ url,
43
+ headers,
44
+ simulationResult,
45
+ }) => {
46
+ const chainInfo = BLOWFISH_EVM_CHAINS[simulationResult.baseAssetName]
47
+ if (!chainInfo) {
48
+ return
49
+ }
50
+
51
+ const { network, chain } = chainInfo
52
+
53
+ const payload = convertToAPIPayload({ address, message, url })
54
+
55
+ const response = await makeSimulationAPICall({
56
+ url: apiEndpoint,
57
+ chain,
58
+ network,
59
+ payload,
60
+ headers,
61
+ })
62
+
63
+ if (!response) {
64
+ return
65
+ }
66
+
67
+ handleActionResponse({ simulationResult, response })
68
+ }
@@ -0,0 +1,265 @@
1
+ import { pick } from '@exodus/basic-utils'
2
+ import { createCurrency, makeSimulationAPICall } from '@exodus/web3-utils'
3
+
4
+ import { BLOWFISH_EVM_CHAINS, MAX_INT256_SOLIDITY } from './common.js'
5
+
6
+ const convertTransactionToApiPayload = (transactions, origin, blockNumber) => {
7
+ const payload = {
8
+ txObjects: transactions.map((transaction) => ({
9
+ from: transaction.from,
10
+ to: transaction.to,
11
+ data: transaction.data,
12
+ value: transaction.value,
13
+ gas: transaction.gas,
14
+ gas_price: transaction.gasPrice,
15
+ })),
16
+ metadata: {
17
+ origin,
18
+ },
19
+ userAccount: transactions[0].from,
20
+ }
21
+
22
+ if (blockNumber !== undefined) {
23
+ payload.blockNumber = blockNumber
24
+ }
25
+
26
+ return payload
27
+ }
28
+
29
+ const createBalanceAmount = (amount, { decimals, symbol }) => {
30
+ const beforeAmount = createCurrency({
31
+ amount: amount.before,
32
+ symbol,
33
+ denominator: decimals,
34
+ })
35
+ const afterAmount = createCurrency({
36
+ amount: amount.after,
37
+ symbol,
38
+ denominator: decimals,
39
+ })
40
+
41
+ return beforeAmount.sub(afterAmount)
42
+ }
43
+
44
+ const handleExpectedBalanceChange = (expectedBalanceChange, simulationResult, { network }) => {
45
+ const { willApprove, willSend, willReceive } = simulationResult.balanceChanges
46
+ const { data, kind } = expectedBalanceChange.rawInfo
47
+
48
+ if (kind === 'FARCASTER_CHANGE_RECOVERY_ADDRESS' || kind === 'ERC20_PERMIT') {
49
+ return
50
+ }
51
+
52
+ const { asset: simulationAsset } = data
53
+
54
+ const isNftTransfer =
55
+ kind === 'ERC721_TRANSFER' || (kind === 'ERC1155_TRANSFER' && !simulationAsset.decimals)
56
+ const isNftTransferApproval =
57
+ kind === 'ERC721_APPROVAL' ||
58
+ (kind === 'ERC721_APPROVAL_FOR_ALL' && !simulationAsset.decimals) ||
59
+ (kind === 'ERC1155_APPROVAL_FOR_ALL' && !simulationAsset.decimals)
60
+ const decimals =
61
+ isNftTransfer || isNftTransferApproval || !Number.isFinite(simulationAsset.decimals)
62
+ ? 0
63
+ : simulationAsset.decimals
64
+ const simulationAssetInfo = pick(simulationAsset, [
65
+ 'address',
66
+ 'imageUrl',
67
+ 'name',
68
+ 'symbol',
69
+ 'verified',
70
+ ])
71
+
72
+ let symbol = simulationAsset.symbol || simulationAsset.name || 'NFT'
73
+ if (network === 'polygon' && simulationAsset.symbol === 'MATIC') {
74
+ symbol = 'POL'
75
+ }
76
+
77
+ if (isNftTransfer || isNftTransferApproval) {
78
+ simulationAssetInfo.imageUrl = data?.metadata?.rawImageUrl
79
+ }
80
+
81
+ if (
82
+ kind === 'ERC20_APPROVAL' ||
83
+ kind === 'ERC721_APPROVAL_FOR_ALL' ||
84
+ kind === 'ERC1155_APPROVAL_FOR_ALL' ||
85
+ kind === 'ERC721_APPROVAL'
86
+ ) {
87
+ const unitsToUse = symbol || simulationAsset.name || 'Units.'
88
+
89
+ const approvingBalance = createCurrency({
90
+ amount: data.amount.after,
91
+ symbol: unitsToUse,
92
+ denominator: decimals,
93
+ })
94
+
95
+ willApprove.push({
96
+ asset: simulationAssetInfo,
97
+ spender: data.spender.address,
98
+ unitName: unitsToUse,
99
+ balance: approvingBalance,
100
+ isMaxApproval: approvingBalance._number.gte(MAX_INT256_SOLIDITY),
101
+ })
102
+ return
103
+ }
104
+
105
+ const { amount } = data
106
+
107
+ const balance = createBalanceAmount(amount, { decimals, symbol })
108
+
109
+ if (balance.isZero) {
110
+ return
111
+ }
112
+
113
+ const balanceChange = {
114
+ asset: simulationAssetInfo,
115
+ balance,
116
+ }
117
+
118
+ if (isNftTransfer || isNftTransferApproval) {
119
+ const tokenId = data.tokenId
120
+ const compositeId = `${simulationAsset.address}/${tokenId}`
121
+ balanceChange.nft = {
122
+ id: `${network}:${compositeId}`,
123
+ compositeId,
124
+ title: simulationAsset.name,
125
+ }
126
+ }
127
+
128
+ if (balance.isNegative) {
129
+ balanceChange.balance = balanceChange.balance.negate()
130
+
131
+ willReceive.push(balanceChange)
132
+ } else {
133
+ willSend.push(balanceChange)
134
+ }
135
+ }
136
+
137
+ const simulationResultHasAssets = (expectedStateChanges) => {
138
+ for (const expectedChange of expectedStateChanges) {
139
+ const { asset: simulationAsset } = expectedChange.rawInfo.data
140
+ if (!simulationAsset) {
141
+ return false
142
+ }
143
+ }
144
+
145
+ return true
146
+ }
147
+
148
+ export const SimulationErrorMessages = {
149
+ APICallFailed: 0,
150
+ SimulationFailed: 1,
151
+ SimulationNotSupported: 2,
152
+ }
153
+
154
+ export const simulateTransactionsApi = async ({
155
+ transactions,
156
+ apiEndpoint,
157
+ origin,
158
+ blockNumber,
159
+ simulationResult,
160
+ headers,
161
+ asset,
162
+ }) => {
163
+ const successDefaultResult = {
164
+ kind: 'success',
165
+ value: undefined,
166
+ }
167
+ if (!BLOWFISH_EVM_CHAINS[asset.name]) {
168
+ return {
169
+ kind: 'error',
170
+ error: SimulationErrorMessages.SimulationNotSupported,
171
+ errorMessage: 'Simulation is not supported for this asset.',
172
+ }
173
+ }
174
+
175
+ const userAddress = transactions[0].from
176
+
177
+ const payload = convertTransactionToApiPayload(transactions, origin, blockNumber)
178
+
179
+ if (!Object.hasOwnProperty.call(BLOWFISH_EVM_CHAINS, asset.name)) {
180
+ return successDefaultResult
181
+ }
182
+
183
+ const { network, chain } = BLOWFISH_EVM_CHAINS[asset.name]
184
+
185
+ const response = await makeSimulationAPICall({
186
+ url: apiEndpoint,
187
+ chain,
188
+ network,
189
+ payload,
190
+ headers,
191
+ })
192
+ if (!response) {
193
+ return {
194
+ kind: 'error',
195
+ error: SimulationErrorMessages.APICallFailed,
196
+ errorMessage: 'Simulation API call failed.',
197
+ }
198
+ }
199
+
200
+ // eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only
201
+ simulationResult.metadata.simulatedLocally = false
202
+
203
+ const { expectedStateChanges = {}, error } = response.simulationResults.aggregated
204
+
205
+ if (error) {
206
+ return {
207
+ kind: 'error',
208
+ error: SimulationErrorMessages.SimulationFailed,
209
+ errorMessage: error?.humanReadableError,
210
+ }
211
+ }
212
+
213
+ const { action } = response
214
+
215
+ if (action === 'BLOCK') {
216
+ simulationResult.warnings.push({
217
+ kind: 'MALICIOUS_ACTION',
218
+ severity: 'CRITICAL',
219
+ message: 'This dApp could be malicious. Do not proceed unless you are certain this is safe.',
220
+ })
221
+ } else if (action === 'WARN') {
222
+ simulationResult.warnings.push({
223
+ kind: 'SUSPICIOUS_ACTION',
224
+ severity: 'MEDIUM',
225
+ message: 'This transaction may be suspicious. Proceed with caution.',
226
+ })
227
+ }
228
+
229
+ const noExpectedStateChangesDetected = Object.keys(expectedStateChanges).length === 0
230
+
231
+ if (noExpectedStateChangesDetected) {
232
+ simulationResult.balanceChanges.willSend.push({
233
+ balance: createCurrency({
234
+ amount: 0,
235
+ symbol: asset.displayTicker,
236
+ denominator: asset.currency.defaultUnit.power,
237
+ }),
238
+ })
239
+
240
+ return successDefaultResult
241
+ }
242
+
243
+ const userExpectedChanges = Object.keys(expectedStateChanges).reduce((prev, address) => {
244
+ if (userAddress.toLowerCase() !== address.toLowerCase()) {
245
+ return prev
246
+ }
247
+
248
+ const result =
249
+ (Object.prototype.hasOwnProperty.call(expectedStateChanges, address) &&
250
+ expectedStateChanges[address]) ||
251
+ []
252
+
253
+ return [...prev, ...result]
254
+ }, [])
255
+
256
+ if (!simulationResultHasAssets(userExpectedChanges)) {
257
+ return successDefaultResult
258
+ }
259
+
260
+ userExpectedChanges.forEach((expectedChange) => {
261
+ handleExpectedBalanceChange(expectedChange, simulationResult, { network })
262
+ })
263
+
264
+ return successDefaultResult
265
+ }
@@ -0,0 +1,16 @@
1
+ import assert from 'minimalistic-assert'
2
+
3
+ import { createSimulateTransactions } from './create-simulate-transactions.js'
4
+
5
+ const simulateEVMTransactions = createSimulateTransactions({
6
+ apiEndpoint: 'https://simulation.a.exodus.io/simulate',
7
+ headers: {
8
+ 'X-Api-Version': '2023-06-05',
9
+ },
10
+ })
11
+
12
+ export function simulateTransactions({ asset, transactions, ...restParameters }) {
13
+ assert(asset, '"asset" should be passed.')
14
+
15
+ return simulateEVMTransactions({ asset, transactions, ...restParameters })
16
+ }
@@ -0,0 +1,52 @@
1
+ import SolidityContract from '@exodus/solidity-contract'
2
+
3
+ export function getDisplayDetails(willApprove) {
4
+ let title = 'Approve Transaction?'
5
+ const isApproveTransaction = willApprove.length
6
+
7
+ if (isApproveTransaction) {
8
+ const tokenName = willApprove[0].unitName
9
+ title = tokenName ? `Approve ${tokenName} Transactions?` : title
10
+ }
11
+
12
+ return { title }
13
+ }
14
+
15
+ export const isSimpleTransfer = (transaction) => {
16
+ return ['0x', null, undefined].includes(transaction.data)
17
+ }
18
+
19
+ export const getTxFeeDetails = (transaction) => {
20
+ const maxFeePerGas = transaction.maxFeePerGas
21
+ const gasPrice = transaction.gasPrice
22
+ const gasLimit = transaction.gas
23
+
24
+ return {
25
+ gasLimit,
26
+ maxFeePerGas,
27
+ gasPrice,
28
+ }
29
+ }
30
+
31
+ export function decodeRecipientAddresses(transaction) {
32
+ try {
33
+ if (!transaction.to) {
34
+ return []
35
+ }
36
+
37
+ if (isSimpleTransfer(transaction)) {
38
+ return [transaction.to]
39
+ }
40
+
41
+ const token = SolidityContract.erc20(transaction.to)
42
+ const { method, values } = token.decodeInput(transaction.data)
43
+
44
+ if (method === 'transfer') {
45
+ return [values[0]]
46
+ }
47
+
48
+ return []
49
+ } catch {
50
+ return []
51
+ }
52
+ }
@@ -0,0 +1,26 @@
1
+ import { estimateSimpleTransfer, NotSimpleTransferError } from './estimate-simple-transfer.js'
2
+
3
+ export const tryEstimatingChangesLocally = ({ asset, simulationResult, transaction }) => {
4
+ if (typeof transaction.from !== 'string' || typeof transaction.to !== 'string') {
5
+ return false
6
+ }
7
+
8
+ try {
9
+ const estimatedValue = estimateSimpleTransfer({ baseAsset: asset, transaction })
10
+ const actualTransferValue =
11
+ transaction.from.toLowerCase() === transaction.to.toLowerCase()
12
+ ? asset.currency.ZERO
13
+ : estimatedValue
14
+ simulationResult.balanceChanges.willSend.push({
15
+ balance: actualTransferValue,
16
+ })
17
+
18
+ return true
19
+ } catch (err) {
20
+ if (!(err instanceof NotSimpleTransferError)) {
21
+ // TODO: accept a logger dep and log the unexpected local simulation error.
22
+ }
23
+
24
+ return false
25
+ }
26
+ }
@@ -3,6 +3,7 @@ import { parseUnsignedTx } from '@exodus/ethereum-lib'
3
3
  import { bufferToHex } from '@exodus/ethereumjs/util'
4
4
  import assert from 'minimalistic-assert'
5
5
 
6
+ import { simulateTransactions as simulateEthereumTransactions } from '../../simulation/simulate-transactions.js'
6
7
  import { EthereumStaking, UNSTAKE_DEFAULTS } from './api.js'
7
8
  import { simulateEverstakeUnstake } from './everstake.js'
8
9
 
@@ -211,7 +212,8 @@ export async function simulateUndelegateTransactions({
211
212
 
212
213
  if (transactions.length === 0) return
213
214
 
214
- const simulationResponse = await asset.baseAsset.api.web3.simulateTransactions({
215
+ const simulationResponse = await simulateEthereumTransactions({
216
+ asset: asset.baseAsset,
215
217
  baseAssetName: asset.baseAsset.name,
216
218
  origin: 'exodus-staking',
217
219
  senderAddress,
@@ -4,6 +4,7 @@ import assetsList, { asset as ethereum } from '@exodus/ethereum-meta'
4
4
  import { Tx } from '@exodus/models'
5
5
  import assert from 'minimalistic-assert'
6
6
 
7
+ import { simulateTransactions as simulateEthereumTransactions } from '../../simulation/simulate-transactions.js'
7
8
  import { decodePolygonStakingTxInputAmount } from '../../tx-log-staking-processor/utils.js'
8
9
  import { MaticStakingApi } from './api.js'
9
10
 
@@ -211,7 +212,8 @@ export async function maticDelegateSimulateTransactions({
211
212
  senderAddress,
212
213
  }
213
214
 
214
- const simulationResponse = await asset.baseAsset.api.web3.simulateTransactions({
215
+ const simulationResponse = await simulateEthereumTransactions({
216
+ asset: asset.baseAsset,
215
217
  ...simulationTxData,
216
218
  transactions: [approveTxRequest, delegateTxRequest],
217
219
  })
@@ -75,6 +75,40 @@ export class ClarityTruncatedHistoryMonitor extends ClarityMonitor {
75
75
  let clarityCursor
76
76
 
77
77
  try {
78
+ // When refreshing, we need to clear out old asset
79
+ // balances in `accountState` since these can surface
80
+ // values populated from alternative monitors i.e.
81
+ // `no-history`, which can be written at arbitrary
82
+ // points in time.
83
+ //
84
+ // Since the `truncated-history-monitor` does not
85
+ // guarantee an `accountState` write for an asset,
86
+ // (these are only provided when the history is
87
+ // truncated), we must ensure older checkpoints cannot
88
+ // be inadvertently relied upon.
89
+ //
90
+ if (refresh) {
91
+ const defaultAccountState = this.asset.api.createAccountState().create()
92
+
93
+ const currentAccountState = derivedData.currentAccountState ?? defaultAccountState
94
+
95
+ const resetTokenBalances = {
96
+ balance: defaultAccountState.balance,
97
+ tokenBalances: defaultAccountState.tokenBalances,
98
+ }
99
+
100
+ // Optimistically update the `derivedData` for downstream.
101
+ derivedData.currentAccountState = currentAccountState.merge(resetTokenBalances)
102
+
103
+ this.aci.updateAccountStateBatch({
104
+ assetName,
105
+ walletAccount,
106
+ accountState: currentAccountState,
107
+ newData: resetTokenBalances,
108
+ batch,
109
+ })
110
+ }
111
+
78
112
  const response = await this.getHistoryFromServer({ walletAccount, derivedData, refresh })
79
113
 
80
114
  ;({ allTxs } = await normalizeTransactionsResponse({
@@ -3,7 +3,7 @@ import { SynchronizedTime } from '@exodus/basic-utils'
3
3
  import { Tx } from '@exodus/models'
4
4
  import lodash from 'lodash'
5
5
 
6
- import { fromHexToString } from '../number-utils.js'
6
+ import { getBatchedRpcBalances } from './monitor-utils/get-batched-rpc-balances.js'
7
7
  import { UNCONFIRMED_TX_LIMIT } from './monitor-utils/get-derive-transactions-to-check.js'
8
8
  import {
9
9
  excludeUnchangedTokenBalances,
@@ -41,28 +41,7 @@ export class EthereumNoHistoryMonitor extends BaseMonitor {
41
41
  }
42
42
 
43
43
  async getBalances({ tokens, ourWalletAddress }) {
44
- const batch = Object.create(null)
45
- const request = this.server.getBalanceRequest(ourWalletAddress)
46
- batch[this.asset.name] = request
47
- for (const token of tokens) {
48
- const request = this.server.balanceOfRequest(ourWalletAddress, token.contract.address)
49
- batch[token.name] = request
50
- }
51
-
52
- const pairs = Object.entries(batch)
53
- if (pairs.length === 0) {
54
- return {}
55
- }
56
-
57
- const requests = pairs.map((pair) => pair[1])
58
- const responses = await this.server.sendBatchRequest(requests)
59
- const entries = pairs.map((pair, idx) => {
60
- const balanceHex = responses[idx]
61
- const name = pair[0]
62
- const balance = fromHexToString(balanceHex)
63
- return [name, balance]
64
- })
65
- return Object.fromEntries(entries)
44
+ return getBatchedRpcBalances({ baseAsset: this.asset, ourWalletAddress, tokens })
66
45
  }
67
46
 
68
47
  async getNewAccountState({ tokens, currentTokenBalances, ourWalletAddress }) {
@@ -0,0 +1,28 @@
1
+ import assert from 'minimalistic-assert'
2
+
3
+ import { fromHexToString } from '../../number-utils.js'
4
+
5
+ export const getBatchedRpcBalances = async ({ baseAsset, tokens, ourWalletAddress }) => {
6
+ assert(baseAsset, 'expected baseAsset')
7
+ assert(Array.isArray(tokens), 'expected array tokens')
8
+ assert(ourWalletAddress, 'expected ourWalletAddress')
9
+
10
+ const [balance, ...tokenBalances] = await tokens
11
+ .reduce(
12
+ (acc, { contract: { address: tokenAddress } }) =>
13
+ acc.balanceOfRequest(ourWalletAddress, tokenAddress),
14
+ baseAsset.createRpcRequestAccumulator().getBalanceRequest(ourWalletAddress)
15
+ )
16
+ .flush(baseAsset.server)
17
+
18
+ return {
19
+ [baseAsset.name]: fromHexToString(balance),
20
+ ...Object.fromEntries(
21
+ tokens.map(({ name, contract: { address: tokenAddress } }, i) => {
22
+ const tokenBalance = tokenBalances[i]?.confirmed?.[tokenAddress]
23
+ assert(tokenBalance != null, `missing token balance for ${name} (${tokenAddress})`)
24
+ return [name, tokenBalance]
25
+ })
26
+ ),
27
+ }
28
+ }
@@ -11,10 +11,15 @@ import { transactionExists } from '../eth-like-util.js'
11
11
  * @param {string} options.txId - The transaction ID.
12
12
  * @param {boolean} options.isHardware - Whether this is a hardware wallet.
13
13
  * @param {string} options.hint - Hint for the error.
14
+ * @param {boolean} options.isBumpTx - Whether the occur occured whilst bumping a Tx.
15
+ * @param {boolean} options.isRetryNonceTx - If the error occurred during a nonce bump.
14
16
  * @returns {Promise<{ shouldRetry: boolean }>} - Returns if nonce too low and can retry.
15
17
  * @throws {EthLikeError} - Throws for all other error cases.
16
18
  */
17
- export const handleBroadcastError = async (err, { asset, txId, isHardware, hint, isBumpTx }) => {
19
+ export const handleBroadcastError = async (
20
+ err,
21
+ { asset, txId, isHardware, hint, isBumpTx, isRetryNonceTx }
22
+ ) => {
18
23
  const message = err.message
19
24
 
20
25
  const errorInfo = getEvmErrorReason(message) || EVM_ERROR_REASONS.broadcastTxFailed
@@ -46,7 +51,7 @@ export const handleBroadcastError = async (err, { asset, txId, isHardware, hint,
46
51
  // NOTE: Don't auto-retry nonce repair for bump/replacement txs.
47
52
  // A replacement must keep the *same nonce* as the tx it's replacing.
48
53
  // If we "fix" a bump tx by advancing the nonce, we create a brand-new tx instead of replacing the pending one.
49
- if (isNonceTooLow && !isHardware && !isBumpTx) return { shouldRetry: true }
54
+ if (isNonceTooLow && !isHardware && !isBumpTx && !isRetryNonceTx) return { shouldRetry: true }
50
55
 
51
56
  throw new EthLikeError({
52
57
  message: err.message,
@@ -100,6 +100,7 @@ const txSendFactory = ({ assetClientInterface, createTx }) => {
100
100
  isHardware: false,
101
101
  hint: safeString`retry:broadcastTx`,
102
102
  isBumpTx: false, // retry path is only for non-bump anyway
103
+ isRetryNonceTx: true,
103
104
  })
104
105
  }
105
106
  }
@@ -1,6 +1,7 @@
1
- import { createSimulateMessage as createSimulateEVMMessage } from '@exodus/web3-ethereum-utils'
2
1
  import assert from 'minimalistic-assert'
3
2
 
3
+ import { createSimulateMessage as createSimulateEVMMessage } from '../simulation/create-simulate-message.js'
4
+
4
5
  export const createSimulateMessage = ({ asset }) => {
5
6
  assert(asset, '"asset" should be passed.')
6
7
 
@@ -1,17 +1,11 @@
1
- import { createSimulateTransactions as createSimulateEVMTransactions } from '@exodus/web3-ethereum-utils'
2
1
  import assert from 'minimalistic-assert'
3
2
 
3
+ import { simulateTransactions as simulateEthereumTransactions } from '../simulation/simulate-transactions.js'
4
+
4
5
  export const createSimulateTransactions = ({ asset }) => {
5
6
  assert(asset, '"asset" should be passed.')
6
7
 
7
- const simulateEVMTransactions = createSimulateEVMTransactions({
8
- apiEndpoint: 'https://simulation.a.exodus.io/simulate',
9
- headers: {
10
- 'X-Api-Version': '2023-06-05',
11
- },
12
- })
13
-
14
8
  return function simulateTransaction({ transactions, ...restParameters }) {
15
- return simulateEVMTransactions({ asset, transactions, ...restParameters })
9
+ return simulateEthereumTransactions({ asset, transactions, ...restParameters })
16
10
  }
17
11
  }