@exodus/ethereum-api 8.42.2 → 8.43.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,24 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [8.43.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.43.0...@exodus/ethereum-api@8.43.1) (2025-07-17)
7
+
8
+ **Note:** Version bump only for package @exodus/ethereum-api
9
+
10
+
11
+
12
+
13
+
14
+ ## [8.43.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.42.2...@exodus/ethereum-api@8.43.0) (2025-07-14)
15
+
16
+
17
+ ### Features
18
+
19
+
20
+ * feat: tx send split, tx-create (#5854)
21
+
22
+
23
+
6
24
  ## [8.42.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.42.1...@exodus/ethereum-api@8.42.2) (2025-07-11)
7
25
 
8
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.42.2",
3
+ "version": "8.43.1",
4
4
  "description": "Transaction monitors, fee monitors, RPC with the blockchain node, and other networking code for Ethereum and EVM-based blockchains",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -28,7 +28,7 @@
28
28
  "@exodus/bip44-constants": "^195.0.0",
29
29
  "@exodus/crypto": "^1.0.0-rc.13",
30
30
  "@exodus/currency": "^6.0.1",
31
- "@exodus/ethereum-lib": "^5.15.1",
31
+ "@exodus/ethereum-lib": "^5.16.0",
32
32
  "@exodus/ethereum-meta": "^2.5.0",
33
33
  "@exodus/ethereumholesky-meta": "^2.0.2",
34
34
  "@exodus/ethereumjs": "^1.0.0",
@@ -63,5 +63,5 @@
63
63
  "type": "git",
64
64
  "url": "git+https://github.com/ExodusMovement/assets.git"
65
65
  },
66
- "gitHead": "b719d7ad24ba1be671d286e2859afc22ab3b7255"
66
+ "gitHead": "ef192865e8886dedb72c280a1b252ff177e20067"
67
67
  }
@@ -60,6 +60,16 @@ export const resolveMonitorSettings = (
60
60
  return { ...defaultResolution, monitorType: overrideMonitorType, serverUrl: overrideServerUrl }
61
61
  }
62
62
 
63
+ const stringifyPrivateTx = (tx) => {
64
+ assert(tx, 'expected tx')
65
+ if (tx instanceof Uint8Array) return `0x${Buffer.from(tx).toString('hex')}`
66
+
67
+ assert(typeof tx === 'string', 'expected string')
68
+ if (tx.startsWith('0x')) return tx
69
+
70
+ return `0x${tx}`
71
+ }
72
+
63
73
  const broadcastPrivateBundleFactory =
64
74
  ({ privacyServer }) =>
65
75
  async ({ txs }) => {
@@ -69,7 +79,7 @@ const broadcastPrivateBundleFactory =
69
79
  await privacyServer.sendRequest(
70
80
  privacyServer.buildRequest({
71
81
  method: 'eth_sendBundle',
72
- params: [{ txs }],
82
+ params: [{ txs: txs.map((tx) => stringifyPrivateTx(tx)) }],
73
83
  })
74
84
  )
75
85
  }
@@ -157,7 +167,7 @@ export const getNonceFactory = ({ assetClientInterface, useAbsoluteBalanceAndNon
157
167
  assert(assetClientInterface, 'expected assetClientInterface')
158
168
  assert(typeof useAbsoluteBalanceAndNonce === 'boolean', 'expected useAbsoluteBalanceAndNonce')
159
169
 
160
- const getNonce = async ({ asset, fromAddress, walletAccount }) => {
170
+ const getNonce = async ({ asset, fromAddress, walletAccount, triedNonce, forceFromNode }) => {
161
171
  assert(asset, 'expected asset')
162
172
  assert(typeof fromAddress === 'string', 'expected fromAddress')
163
173
  assert(walletAccount, 'expected walletAccount')
@@ -171,6 +181,8 @@ export const getNonceFactory = ({ assetClientInterface, useAbsoluteBalanceAndNon
171
181
  asset,
172
182
  fromAddress,
173
183
  txLog,
184
+ triedNonce,
185
+ forceFromNode,
174
186
  // For assets where we'll fall back to querying the coin node, we
175
187
  // search for pending transactions. For base assets with history,
176
188
  // we'll fall back to the `TxLog` since this also has a knowledge
@@ -37,6 +37,7 @@ import getFeeAsyncFactory from './get-fee-async.js'
37
37
  import { estimateL1DataFeeFactory, getL1GetFeeFactory } from './optimism-gas/index.js'
38
38
  import { serverBasedFeeMonitorFactoryFactory } from './server-based-fee-monitor.js'
39
39
  import { createStakingApi } from './staking-api.js'
40
+ import { createTxFactory } from './tx-create.js'
40
41
  import { txSendFactory } from './tx-send/index.js'
41
42
  import { createWeb3API } from './web3/index.js'
42
43
 
@@ -221,10 +222,15 @@ export const createAssetFactory = ({
221
222
 
222
223
  const createUnsignedTx = createUnsignedTxFactory({ chainId })
223
224
 
225
+ const createTx = createTxFactory({
226
+ chainId,
227
+ assetClientInterface,
228
+ useAbsoluteNonce: useAbsoluteBalanceAndNonce,
229
+ })
230
+
224
231
  const sendTx = txSendFactory({
225
232
  assetClientInterface,
226
- createUnsignedTx,
227
- useAbsoluteBalanceAndNonce,
233
+ createTx,
228
234
  })
229
235
 
230
236
  const estimateL1DataFee = l1GasOracleAddress
@@ -246,6 +252,7 @@ export const createAssetFactory = ({
246
252
  createFeeMonitor,
247
253
  createHistoryMonitor,
248
254
  createToken,
255
+ createTx,
249
256
  createUnsignedTx,
250
257
  customFees: createCustomFeesApi({ baseAsset: asset }),
251
258
  defaultAddressPath,
@@ -255,7 +262,7 @@ export const createAssetFactory = ({
255
262
  getBalanceForAddress: createGetBalanceForAddress({ asset, server }),
256
263
  getConfirmationsNumber: () => confirmationsNumber,
257
264
  getDefaultAddressPath: () => defaultAddressPath,
258
- getFeeAsync: getFeeAsyncFactory({ assetClientInterface, gasLimit, createUnsignedTx }),
265
+ getFeeAsync: getFeeAsyncFactory({ assetClientInterface, gasLimit, createTx }),
259
266
  getFee,
260
267
  getFeeData: () => feeData,
261
268
  getKeyIdentifier: createGetKeyIdentifier({
@@ -114,6 +114,7 @@ export async function fetchGasLimit({
114
114
  toAddress: providedToAddress,
115
115
  txInput: providedTxInput,
116
116
  amount: providedAmount,
117
+ contractAddress,
117
118
  bip70,
118
119
  throwOnError = true,
119
120
  }) {
@@ -146,7 +147,7 @@ export async function fetchGasLimit({
146
147
  toAddress: providedToAddress,
147
148
  })
148
149
 
149
- const txToAddress = isToken ? asset.contract.address : toAddress
150
+ const txToAddress = contractAddress ?? (isToken ? asset.contract.address : toAddress)
150
151
  const txAmount = isToken ? asset.baseAsset.currency.ZERO : amount
151
152
 
152
153
  try {
@@ -1,89 +1,21 @@
1
1
  import assert from 'minimalistic-assert'
2
2
 
3
- import { ARBITRARY_ADDRESS, fetchGasLimit, resolveDefaultTxInput } from './gas-estimation.js'
4
- import { getFeeFactory } from './get-fee.js'
5
- import { getNftArguments } from './nft-utils.js'
3
+ import { getExtraFeeData } from './get-fee.js'
6
4
 
7
- const getFeeAsyncFactory = ({
8
- assetClientInterface,
9
- gasLimit: defaultGasLimit,
10
- createUnsignedTx,
11
- }) => {
5
+ const getFeeAsyncFactory = ({ assetClientInterface, createTx }) => {
12
6
  assert(assetClientInterface, 'assetClientInterface is required')
13
- assert(createUnsignedTx, 'createUnsignedTx is required')
14
- const getFee = getFeeFactory({ gasLimit: defaultGasLimit })
15
- return async ({
16
- nft,
17
- asset,
18
- // provided are values from the UI or other services, they could be undefined
19
- fromAddress: providedFromAddress,
20
- toAddress: providedToAddress,
21
- amount: providedAmount,
22
- txInput: providedTxInput,
23
- gasLimit: providedGasLimit,
24
- bip70,
25
- customFee,
26
- feeData,
27
- }) => {
28
- const fromAddress = providedFromAddress || ARBITRARY_ADDRESS // sending from a random address
29
- const toAddress = providedToAddress || ARBITRARY_ADDRESS // sending to a random address,
30
- const amount = providedAmount ?? asset.currency.ZERO
31
- const resolveGasLimit = async () => {
32
- if (nft) {
33
- return getNftArguments({ asset, nft, fromAddress, toAddress })
34
- }
35
-
36
- const txInput = providedTxInput || resolveDefaultTxInput({ asset, toAddress, amount })
37
- if (providedGasLimit) return { gasLimit: providedGasLimit, txInput }
38
-
39
- const gasLimit = await fetchGasLimit({
40
- asset,
41
- fromAddress: providedFromAddress,
42
- toAddress: providedToAddress,
43
- txInput,
44
- amount,
45
- bip70,
46
- feeData,
47
- })
48
- return { gasLimit, txInput }
49
- }
50
-
51
- const { txInput, gasLimit, contractAddress } = await resolveGasLimit()
52
-
53
- const { fee, gasPrice, ...rest } = getFee({
54
- asset,
55
- feeData,
56
- gasLimit,
57
- amount,
58
- customFee,
59
- })
60
-
61
- const optimismL1DataFee = asset.baseAsset.estimateL1DataFee
62
- ? await asset.baseAsset.estimateL1DataFee({
63
- unsignedTx: createUnsignedTx({
64
- asset,
65
- address: contractAddress || toAddress,
66
- fromAddress,
67
- amount,
68
- nonce: 0,
69
- txInput,
70
- gasPrice,
71
- gasLimit,
72
- }),
73
- })
74
- : undefined
75
-
76
- const l1DataFee = optimismL1DataFee
77
- ? asset.baseAsset.currency.baseUnit(optimismL1DataFee)
78
- : asset.baseAsset.currency.ZERO
79
-
7
+ assert(createTx, 'createTx is required')
8
+
9
+ return async (params) => {
10
+ const { asset } = params
11
+ const { unsignedTx } = params.unsignedTx ? params : await createTx(params)
12
+ const fee = asset.feeAsset.currency.parse(unsignedTx.txMeta.fee)
13
+ const coinAmount = asset.currency.parse(unsignedTx.txMeta.amount)
14
+ const extraFeeData = getExtraFeeData({ asset, amount: coinAmount })
80
15
  return {
81
- fee: fee.add(l1DataFee),
82
- // TODO: Should this be `l1DataFee`?
83
- optimismL1DataFee,
84
- gasLimit,
85
- gasPrice,
86
- ...rest,
16
+ fee,
17
+ extraFeeData,
18
+ unsignedTx,
87
19
  }
88
20
  }
89
21
  }
package/src/get-fee.js CHANGED
@@ -11,7 +11,7 @@ const taxes = {
11
11
  paxgold: 0.0002,
12
12
  }
13
13
 
14
- const getExtraFeeData = ({ asset, amount }) => {
14
+ export const getExtraFeeData = ({ asset, amount }) => {
15
15
  const tax = taxes[asset.name]
16
16
  if (!amount || !tax || amount.isZero) {
17
17
  return {}
package/src/index.js CHANGED
@@ -83,9 +83,7 @@ export {
83
83
 
84
84
  export { reasons as errorReasons, withErrorReason, EthLikeError } from './error-wrapper.js'
85
85
 
86
- // TODO: `getFeeInfo` is not consumed by third parties and should
87
- // be considered an internal API.
88
- export { txSendFactory, getFeeInfo } from './tx-send/index.js'
86
+ export { txSendFactory } from './tx-send/index.js'
89
87
 
90
88
  export { createAssetFactory } from './create-asset.js'
91
89
 
@@ -0,0 +1,340 @@
1
+ import { calculateBumpedGasPrice, currency2buffer, isEthereumLikeToken } from '@exodus/ethereum-lib'
2
+ import createEthereumJsTx from '@exodus/ethereum-lib/src/unsigned-tx/create-ethereumjs-tx.js'
3
+ import assert from 'minimalistic-assert'
4
+
5
+ import * as ErrorWrapper from './error-wrapper.js'
6
+ import { isContractAddressCached } from './eth-like-util.js'
7
+ import { ensureSaneEip1559GasPriceForTipGasPrice } from './fee-utils.js'
8
+ import { ARBITRARY_ADDRESS, fetchGasLimit, resolveDefaultTxInput } from './gas-estimation.js'
9
+ import { getFeeFactoryGasPrices } from './get-fee.js'
10
+ import { getNftArguments } from './nft-utils.js'
11
+
12
+ async function createUnsignedTxWithFees({
13
+ asset,
14
+ chainId,
15
+ to, // the tx to address, it could be the reciver address for native sending, the token contract, the DEX contract, etc
16
+ value, // the value of the tx in NU, it can be the value in eth or 0 when calling contracts
17
+ data, // the data of the tx in hex string, it can be 0x for native sending or the params when sending to a contract
18
+ gasLimit,
19
+ eip1559Enabled,
20
+ gasPrice, // eip 1559: `maxFeePerGas`
21
+ tipGasPrice, // eip 1559: `maxPriorityPerGas`
22
+ nonce,
23
+ bumpTxId,
24
+ coinAmount, // coinAmount
25
+ fromAddress, // user's sending address
26
+ toAddress, // user's receiver address
27
+ }) {
28
+ assert(asset, 'asset is required')
29
+ assert(typeof chainId === 'number', 'chainId is required')
30
+ assert(to, 'to is required')
31
+ assert(value, 'value is required')
32
+ assert(data, 'data is required')
33
+ assert(gasLimit, 'gasLimit is required')
34
+ assert(gasPrice, 'gasPrice is required')
35
+ assert(coinAmount, 'coinAmount is required')
36
+ assert(fromAddress, 'fromAddress is required')
37
+ assert(toAddress, 'toAddress is required')
38
+ assert(typeof eip1559Enabled === 'boolean', 'eip1559Enabled is required')
39
+
40
+ const ethjsTx = createEthereumJsTx({
41
+ txData: {
42
+ nonce,
43
+ gasPrice: currency2buffer(gasPrice),
44
+ tipGasPrice: tipGasPrice ? currency2buffer(tipGasPrice) : undefined,
45
+ gasLimit,
46
+ to,
47
+ value: currency2buffer(value),
48
+ data,
49
+ chainId,
50
+ },
51
+ txMeta: {
52
+ eip1559Enabled,
53
+ },
54
+ })
55
+ const transactionBuffer = ethjsTx.serialize()
56
+
57
+ const baseFee = gasPrice.mul(gasLimit)
58
+ const optimismL1DataFee = asset.baseAsset.estimateL1DataFee
59
+ ? await asset.baseAsset.estimateL1DataFee({
60
+ unsignedTx: { txData: { transactionBuffer, chainId } },
61
+ })
62
+ : undefined
63
+
64
+ const l1DataFee = optimismL1DataFee
65
+ ? asset.baseAsset.currency.baseUnit(optimismL1DataFee)
66
+ : asset.baseAsset.currency.ZERO
67
+
68
+ const fee = baseFee.add(l1DataFee)
69
+
70
+ const unsignedTx = {
71
+ txData: { transactionBuffer, chainId },
72
+ txMeta: {
73
+ bumpTxId,
74
+ eip1559Enabled,
75
+ fromAddress,
76
+ toAddress,
77
+ amount: coinAmount.toDefaultString({ unit: true }),
78
+ fee: fee.toDefaultString({ unit: true }),
79
+ },
80
+ }
81
+ return { unsignedTx }
82
+ }
83
+
84
+ const createBumpUnsignedTx = async ({
85
+ fromAddress,
86
+ chainId,
87
+ asset,
88
+ bumpTxId,
89
+ baseAssetTxLog,
90
+ assetClientInterface,
91
+ walletAccount,
92
+ feeData,
93
+ nonce: providedNonce,
94
+ }) => {
95
+ const baseAsset = asset.baseAsset
96
+ const replacedTx = baseAssetTxLog.get(bumpTxId)
97
+ const assets = await assetClientInterface.getAssetsForNetwork({ baseAssetName: baseAsset.name })
98
+ if (!replacedTx || !replacedTx.pending) {
99
+ throw new Error(`Cannot bump transaction ${bumpTxId}: not found or confirmed`)
100
+ }
101
+
102
+ let replacedTokenTx
103
+ if (replacedTx.tokens.length > 0) {
104
+ const [tokenAssetName] = replacedTx.tokens
105
+ const tokenTxSet = await assetClientInterface.getTxLog({
106
+ assetName: tokenAssetName,
107
+ walletAccount,
108
+ })
109
+ replacedTokenTx = tokenTxSet.get(bumpTxId)
110
+
111
+ if (replacedTokenTx) {
112
+ // Attempt to overwrite the asset to reflect the fact that
113
+ // we're performing a token transaction.
114
+ asset = assets[tokenAssetName]
115
+ if (!asset) {
116
+ throw new Error(
117
+ `unable to find ${tokenAssetName} during token bump transaction: asset was not available in assetsForNetwork`
118
+ )
119
+ }
120
+ }
121
+ }
122
+
123
+ const toAddress = (replacedTokenTx || replacedTx).to
124
+ const isToken = isEthereumLikeToken(asset)
125
+ const txToAddress = isToken ? asset.contract.address : toAddress
126
+ const coinAmount = (replacedTokenTx || replacedTx).coinAmount.negate()
127
+ const gasLimit = replacedTx.data.gasLimit
128
+
129
+ const value = isToken ? baseAsset.currency.ZERO : coinAmount
130
+
131
+ const {
132
+ gasPrice: currentGasPrice,
133
+ baseFeePerGas: currentBaseFee,
134
+ eip1559Enabled,
135
+ tipGasPrice: currentTipGasPrice,
136
+ } = feeData
137
+ const { bumpedGasPrice, bumpedTipGasPrice } = calculateBumpedGasPrice({
138
+ baseAsset,
139
+ tx: replacedTx,
140
+ currentGasPrice,
141
+ currentBaseFee,
142
+ currentTipGasPrice,
143
+ eip1559Enabled,
144
+ })
145
+ const gasPrice = bumpedGasPrice
146
+ const tipGasPrice = bumpedTipGasPrice
147
+ const nonce = replacedTx.data.nonce
148
+ const data = replacedTokenTx
149
+ ? asset.contract.transfer.build(toAddress.toLowerCase(), coinAmount.toBaseString())
150
+ : replacedTx.data.data || '0x'
151
+
152
+ if (nonce === undefined) {
153
+ throw new Error(`Cannot bump transaction ${bumpTxId}: data object seems to be corrupted`)
154
+ }
155
+
156
+ // If we have evaluated a bump transaction and the `providedNonce` differs
157
+ // from the `bumpNonce`, we've encountered a conflict and cannot respect
158
+ // the caller's request.
159
+ if (typeof nonce === 'number' && typeof providedNonce === 'number' && nonce !== providedNonce)
160
+ throw new ErrorWrapper.EthLikeError({
161
+ message: new Error('incorrect nonce for replacement transaction'),
162
+ reason: ErrorWrapper.reasons.bumpTxFailed,
163
+ hint: 'providedNonce',
164
+ })
165
+
166
+ return createUnsignedTxWithFees({
167
+ asset,
168
+ chainId,
169
+ to: txToAddress,
170
+ value,
171
+ data,
172
+ gasLimit,
173
+ gasPrice,
174
+ tipGasPrice,
175
+ nonce,
176
+ bumpTxId,
177
+ coinAmount,
178
+ fromAddress,
179
+ toAddress,
180
+ eip1559Enabled,
181
+ })
182
+ }
183
+
184
+ export const createTxFactory = ({ chainId, assetClientInterface, useAbsoluteNonce }) => {
185
+ assert(assetClientInterface, 'assetClientInterface is required')
186
+ assert(typeof chainId === 'number', 'chainId is required')
187
+ return async ({
188
+ asset,
189
+ walletAccount,
190
+ feeData,
191
+ nft, // when sending nfts
192
+ fromAddress: providedFromAddress, // wallet from address
193
+ toAddress: providedToAddress, // user's to address, not the token or the dex contract
194
+ contractAddress: providedContractAddress, // Provided when swapping a token via the DEX contract, not via the token's contract
195
+ txInput: providedTxInput, // Provided when swapping via a DEX contract
196
+ gasLimit: providedGasLimit, // Provided by exchange when known
197
+ amount: providedAmount, // The NU amount to be sent, to be included in the tx value or tx input
198
+ nonce: providedNonce,
199
+ tipGasPrice: providedTipGasPrice,
200
+ gasPrice: providedGasPrice,
201
+ bip70,
202
+ isExchange,
203
+ customFee,
204
+ isSendAll,
205
+ bumpTxId,
206
+ keepTxInput, // @deprecated this flag is used by swaps when swapping a token via DEX. The asset is token but the tx TO address is not the token address. Update swap to use `contractAddress`
207
+ }) => {
208
+ assert(asset, 'asset is required')
209
+ assert(feeData, 'feeData is required')
210
+ const fromAddress = providedFromAddress || ARBITRARY_ADDRESS
211
+
212
+ const baseAssetTxLog = await assetClientInterface.getTxLog({
213
+ assetName: asset.baseAsset.name,
214
+ walletAccount,
215
+ })
216
+
217
+ if (bumpTxId) {
218
+ return createBumpUnsignedTx({
219
+ chainId,
220
+ asset,
221
+ fromAddress,
222
+ bumpTxId,
223
+ baseAssetTxLog,
224
+ assetClientInterface,
225
+ walletAccount,
226
+ feeData,
227
+ nonce: providedNonce,
228
+ })
229
+ }
230
+
231
+ const toAddress = providedToAddress || ARBITRARY_ADDRESS
232
+ const {
233
+ gasPrice: maybeGasPrice,
234
+ feeData: { tipGasPrice: maybeTipGasPrice, eip1559Enabled },
235
+ } = getFeeFactoryGasPrices({ customFee, feeData })
236
+
237
+ const isToken = isEthereumLikeToken(asset)
238
+
239
+ const resolvedGasPrice = providedGasPrice ?? maybeGasPrice
240
+
241
+ const txToAddress =
242
+ providedContractAddress ?? (isToken && !keepTxInput ? asset.contract.address : toAddress)
243
+
244
+ const isContractToAddress = await isContractAddressCached({ asset, address: txToAddress })
245
+
246
+ // HACK: We cannot ensure the no dust invariant for `isSendAll`
247
+ // transactions to contract addresses, since we may be
248
+ // performing a raw token transaction and the parameter
249
+ // applies to the token and not the native amount.
250
+ //
251
+ // Contracts have nondeterministic gas most of the time
252
+ // versus estimations, anyway.
253
+ const isSendAllBaseAsset = isSendAll && !isToken && !isContractToAddress
254
+
255
+ // For native send all transactions, we have to make sure that
256
+ // the `tipGasPrice` is equal to the `gasPrice`, since this is
257
+ // effectively like saying that the `maxFeePerGas` is equal
258
+ // to the `maxPriorityFeePerGas`. We do this so that for a
259
+ // fixed gas cost transaction, no dust balance should remain,
260
+ // since any deviation in the underlying `baseFeePerGas` will
261
+ // result only affect the tip for the miner - no dust remains.
262
+ const tipGasPrice =
263
+ providedTipGasPrice ??
264
+ (eip1559Enabled && isSendAllBaseAsset ? resolvedGasPrice : maybeTipGasPrice)
265
+
266
+ const gasPrice = eip1559Enabled
267
+ ? ensureSaneEip1559GasPriceForTipGasPrice({
268
+ gasPrice: resolvedGasPrice,
269
+ tipGasPrice,
270
+ })
271
+ : resolvedGasPrice
272
+
273
+ const nonce =
274
+ providedNonce ?? (await asset.baseAsset.getNonce({ asset, fromAddress, walletAccount }))
275
+
276
+ if (nft) {
277
+ const {
278
+ contractAddress: txToAddress,
279
+ gasLimit,
280
+ txInput,
281
+ } = await getNftArguments({
282
+ asset,
283
+ nft,
284
+ fromAddress,
285
+ toAddress: providedToAddress,
286
+ })
287
+
288
+ const value = asset.baseAsset.currency.ZERO
289
+
290
+ return createUnsignedTxWithFees({
291
+ chainId,
292
+ asset,
293
+ to: txToAddress,
294
+ value,
295
+ data: txInput,
296
+ gasLimit,
297
+ gasPrice,
298
+ tipGasPrice,
299
+ nonce,
300
+ coinAmount: value,
301
+ fromAddress,
302
+ toAddress,
303
+ eip1559Enabled,
304
+ })
305
+ }
306
+
307
+ const amount = providedAmount ?? asset.currency.ZERO
308
+
309
+ const value = isToken ? asset.baseAsset.currency.ZERO : amount
310
+ const txInput = providedTxInput || resolveDefaultTxInput({ asset, toAddress, amount })
311
+ const gasLimit =
312
+ providedGasLimit ??
313
+ (await fetchGasLimit({
314
+ asset,
315
+ feeData,
316
+ fromAddress: providedFromAddress,
317
+ toAddress: providedToAddress,
318
+ txInput: providedTxInput,
319
+ contractAddress: txToAddress,
320
+ bip70,
321
+ amount,
322
+ }))
323
+
324
+ return createUnsignedTxWithFees({
325
+ asset,
326
+ chainId,
327
+ to: txToAddress,
328
+ value,
329
+ data: txInput,
330
+ gasLimit,
331
+ gasPrice,
332
+ tipGasPrice,
333
+ nonce,
334
+ coinAmount: amount,
335
+ fromAddress,
336
+ toAddress,
337
+ eip1559Enabled,
338
+ })
339
+ }
340
+ }
@@ -1,2 +1 @@
1
1
  export { default as txSendFactory } from './tx-send.js'
2
- export { default as getFeeInfo } from './get-fee-info.js'
@@ -5,7 +5,7 @@ export const resolveNonce = async ({
5
5
  forceFromNode,
6
6
  fromAddress,
7
7
  providedNonce,
8
- txLog,
8
+ txLog = [],
9
9
  triedNonce,
10
10
  tag = 'latest', // use 'pending' for unconfirmed txs
11
11
  useAbsoluteNonce,
@@ -1,230 +1,97 @@
1
- /* eslint-disable @exodus/export-default/last */
2
-
3
- import { calculateBumpedGasPrice, isEthereumLikeToken, normalizeTxId } from '@exodus/ethereum-lib'
1
+ import {
2
+ isEthereumLikeToken,
3
+ normalizeTxId,
4
+ parseUnsignedTx,
5
+ updateNonce,
6
+ } from '@exodus/ethereum-lib'
4
7
  import assert from 'minimalistic-assert'
5
8
 
6
9
  import * as ErrorWrapper from '../error-wrapper.js'
7
- import { isContractAddressCached, transactionExists } from '../eth-like-util.js'
8
- import { getNftArguments } from '../nft-utils.js'
9
- import getFeeInfo from './get-fee-info.js'
10
- import { resolveNonce } from './nonce-utils.js'
11
-
12
- // Exodus enforces a strict invariant that `sendAll` transactions
13
- // must not leave any dust in the sender's account. Currently, the
14
- // assets library has the expectation that the client frontend
15
- // should calculate the precise amount to send, but due to the
16
- // sheer amount of variables involved when resolving a `gasPrice`,
17
- // this is a significant undertaking.
18
- //
19
- // Therefore, although clients try their very best to calculate
20
- // the correct amount, in cases this fails we can fall back to
21
- // the implementation defined here with a warning.
22
- // eslint-disable-next-line camelcase
23
- export const HACK_maybeRefineSendAllAmount = async ({
24
- amount: providedAmount,
25
- asset,
26
- assetClientInterface,
27
- walletAccount,
28
- gasLimit,
29
- gasPrice,
30
- }) => {
31
- try {
32
- const { name: assetName, estimateL1DataFee } = asset
33
-
34
- // HACK: For the interim, we won't attempt to
35
- // reconcile transaction dust on L2s due
36
- // to the nondeterminism about the calldata
37
- // fee.
38
- if (typeof estimateL1DataFee === 'function') return null
39
-
40
- const [txLog, accountState] = await Promise.all([
41
- assetClientInterface.getTxLog({
42
- assetName,
43
- walletAccount,
44
- }),
45
- assetClientInterface.getAccountState({
46
- assetName,
47
- walletAccount,
48
- }),
49
- ])
50
-
51
- const { spendable } = await asset.api.getBalances({ asset, txLog, accountState })
52
- const maxGasCost = gasPrice.mul(gasLimit)
53
-
54
- if (maxGasCost.gt(spendable)) throw new Error('transaction gas cost exceeds spendable balance')
10
+ import { transactionExists } from '../eth-like-util.js'
11
+ import { ARBITRARY_ADDRESS } from '../gas-estimation.js'
55
12
 
56
- const expectedSendAllAmount = spendable.sub(maxGasCost)
13
+ const txSendFactory = ({ assetClientInterface, createTx }) => {
14
+ assert(assetClientInterface, 'assetClientInterface is required')
15
+ assert(createTx, 'createTx is required')
57
16
 
58
- // If the client attempted to send the correct
59
- // amount, good job! You get a cookie!
60
- if (providedAmount.equals(expectedSendAllAmount)) return null
17
+ async function signTx({ asset, unsignedTx, walletAccount }) {
18
+ const { rawTx, txId } = await assetClientInterface.signTransaction({
19
+ assetName: asset.baseAsset.name,
20
+ unsignedTx,
21
+ walletAccount,
22
+ })
61
23
 
62
- // The client attempted to `sendAll` using the incorrect amount.
63
- return expectedSendAllAmount
64
- } catch (e) {
65
- console.error('failed to refine send all amount', e)
66
- return null
24
+ return { rawTx, txId: normalizeTxId(txId) }
67
25
  }
68
- }
69
-
70
- const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBalanceAndNonce }) => {
71
- assert(assetClientInterface, 'assetClientInterface is required')
72
- assert(createUnsignedTx, 'createUnsignedTx is required')
73
- return async ({ asset, walletAccount, address, amount, feeData: maybeFeeData, options = {} }) => {
74
- const {
75
- nft,
76
- bumpTxId,
77
- nonce: providedNonce,
78
- customFee,
79
- keepTxInput,
80
- isSendAll,
81
- isHardware,
82
- isPrivate,
83
- } = options
84
- let { txInput, feeAmount: providedFeeAmount } = options // avoid let!
85
-
86
- const feeOpts = {
87
- gasPrice: options.gasPrice,
88
- tipGasPrice: options.tipGasPrice,
89
- gasLimit: options.gasLimit,
90
- }
91
26
 
27
+ return async ({ asset, walletAccount, unsignedTx: providedUnsignedTx, ...legacyParams }) => {
92
28
  const assetName = asset.name
93
29
  const baseAsset = asset.baseAsset
94
30
 
95
- const assets = await assetClientInterface.getAssetsForNetwork({ baseAssetName: baseAsset.name })
96
-
97
- const feeData =
98
- maybeFeeData ||
99
- (await assetClientInterface.getFeeData({
100
- assetName: baseAsset.name,
101
- }))
31
+ const resolveUnsignedTx = async () => {
32
+ if (providedUnsignedTx) {
33
+ return { unsignedTx: providedUnsignedTx }
34
+ }
102
35
 
103
- const { eip1559Enabled } = feeData
36
+ const feeData =
37
+ legacyParams.feeData ??
38
+ (await assetClientInterface.getFeeData({
39
+ assetName: baseAsset.name,
40
+ }))
104
41
 
105
- const fromAddress = await assetClientInterface.getReceiveAddress({
106
- assetName: baseAsset.name,
107
- walletAccount,
108
- })
42
+ const fromAddress =
43
+ legacyParams.fromAddress ??
44
+ (await assetClientInterface.getReceiveAddress({
45
+ assetName: baseAsset.name,
46
+ walletAccount,
47
+ }))
109
48
 
110
- let contractAddress
111
- if (nft) {
112
- const nftArguments = await getNftArguments({ asset, nft, fromAddress, toAddress: address })
113
- contractAddress = nftArguments.contractAddress
114
- feeOpts.gasLimit = nftArguments.gasLimit
115
- txInput = nftArguments.txInput
116
- amount = asset.baseAsset.currency.ZERO
49
+ return createTx({
50
+ asset,
51
+ walletAccount,
52
+ feeData,
53
+ fromAddress,
54
+ toAddress: legacyParams.address,
55
+ ...legacyParams,
56
+ ...legacyParams.options,
57
+ })
117
58
  }
118
59
 
119
- let bumpNonce
120
-
121
- const baseAssetTxLog = await assetClientInterface.getTxLog({
122
- assetName: baseAsset.name,
123
- walletAccount,
124
- })
125
-
126
- // `replacedTx` is always an ETH/ETC transaction (not a token)
127
- let replacedTx, replacedTokenTx
128
- if (bumpTxId) {
129
- replacedTx = baseAssetTxLog.get(bumpTxId)
60
+ const { unsignedTx } = await resolveUnsignedTx()
130
61
 
131
- if (!replacedTx || !replacedTx.pending) {
132
- throw new Error(`Cannot bump transaction ${bumpTxId}: not found or confirmed`)
133
- }
62
+ // this converts an transactionBuffer to values we can use when creating the tx logs
63
+ const parsedTx = parseUnsignedTx({ asset, unsignedTx })
134
64
 
135
- if (replacedTx.tokens.length > 0) {
136
- const [tokenAssetName] = replacedTx.tokens
137
- const tokenTxSet = await assetClientInterface.getTxLog({
138
- assetName: tokenAssetName,
139
- walletAccount,
140
- })
141
- replacedTokenTx = tokenTxSet.get(bumpTxId)
65
+ // the txMeta.fee may include implicit l1 fees
66
+ const feeAmount = unsignedTx.txMeta.fee
67
+ ? asset.feeAsset.currency.parse(unsignedTx.txMeta.fee)
68
+ : parsedTx.fee
142
69
 
143
- if (replacedTokenTx) {
144
- // Attempt to overwrite the asset to reflect the fact that
145
- // we're performing a token transaction.
146
- asset = assets[tokenAssetName]
147
- if (!asset) {
148
- console.warn(
149
- `unable to find ${tokenAssetName} during token bump transaction: asset was not available in assetsForNetwork`
150
- )
151
- }
152
- }
70
+ let nonce = parsedTx.nonce
153
71
 
154
- // TODO: Should we `throw` if we can't find the asset?
155
- }
156
-
157
- address = (replacedTokenTx || replacedTx).to
158
- amount = (replacedTokenTx || replacedTx).coinAmount.negate()
159
- feeOpts.gasLimit = replacedTx.data.gasLimit
72
+ const tipGasPrice = parsedTx.tipGasPrice
73
+ const gasLimit = parsedTx.gasLimit
74
+ const amount = parsedTx.amount
75
+ const to = parsedTx.to
76
+ const eip1559Enabled = parsedTx.eip1559Enabled
160
77
 
161
- const {
162
- gasPrice: currentGasPrice,
163
- baseFeePerGas: currentBaseFee,
164
- tipGasPrice: currentTipGasPrice,
165
- } = feeData
166
- const { bumpedGasPrice, bumpedTipGasPrice } = calculateBumpedGasPrice({
167
- baseAsset,
168
- tx: replacedTx,
169
- currentGasPrice,
170
- currentBaseFee,
171
- currentTipGasPrice,
172
- eip1559Enabled,
173
- })
174
- feeOpts.gasPrice = bumpedGasPrice
175
- feeOpts.tipGasPrice = bumpedTipGasPrice
176
- bumpNonce = replacedTx.data.nonce
177
- txInput = replacedTokenTx ? null : replacedTx.data.data || '0x'
178
- if (bumpNonce === undefined) {
179
- throw new Error(`Cannot bump transaction ${bumpTxId}: data object seems to be corrupted`)
180
- }
181
- }
78
+ // unknown data from buffer...
79
+ const fromAddress = unsignedTx.txMeta.fromAddress
80
+ const selfSend = fromAddress === to
81
+ const replacedTxId = unsignedTx.txMeta.bumpTxId
182
82
 
183
- // If we have evaluated a bump transaction and the `providedNonce` differs
184
- // from the `bumpNonce`, we've encountered a conflict and cannot respect
185
- // the caller's request.
186
- if (
187
- typeof bumpNonce === 'number' &&
188
- typeof providedNonce === 'number' &&
189
- bumpNonce !== providedNonce
83
+ assert(
84
+ to.toLowerCase() !== ARBITRARY_ADDRESS,
85
+ `The receiving wallet address must not be ${ARBITRARY_ADDRESS}`
190
86
  )
191
- throw new ErrorWrapper.EthLikeError({
192
- message: new Error('incorrect nonce for replacement transaction'),
193
- reason: ErrorWrapper.reasons.bumpTxFailed,
194
- hint: 'providedNonce',
195
- })
196
-
197
- // Choose a nonce that compensates for transctions which are currently
198
- // in pending; for example, when we send transactions at low gas which
199
- // will be stored by `geth` for execution at a later point in time.
200
- //
201
- // When we are not intentionally bumping a transaction, users are
202
- // appending a new transaction to the chain - therefore we should
203
- // be mindful of nonces belonging to us which are currently pending.
204
- const resolvedNonce =
205
- providedNonce ??
206
- bumpNonce ??
207
- (await asset.baseAsset.getNonce({ asset, fromAddress, walletAccount }))
208
87
 
209
- const createTxParams = {
210
- assetClientInterface,
88
+ let { txId, rawTx } = await signTx({
211
89
  asset,
90
+ unsignedTx,
212
91
  walletAccount,
213
- toAddress: contractAddress || address,
214
- amount,
215
- nonce: resolvedNonce,
216
- fromAddress,
217
- customFee,
218
- feeOpts,
219
- txInput,
220
- keepTxInput,
221
- isSendAll,
222
- createUnsignedTx,
223
- feeData,
224
- providedFeeAmount,
225
- }
92
+ })
226
93
 
227
- let { txId, rawTx, nonce, gasLimit, tipGasPrice, feeAmount } = await createTx(createTxParams)
94
+ const isPrivate = Boolean(legacyParams?.options?.isPrivate)
228
95
 
229
96
  if (isPrivate && typeof baseAsset.broadcastPrivateTx !== 'function')
230
97
  throw new Error(
@@ -250,7 +117,7 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
250
117
  reason: ErrorWrapper.reasons.insufficientFunds,
251
118
  hint: 'broadcastTx',
252
119
  })
253
- } else if (bumpTxId) {
120
+ } else if (unsignedTx.txMeta.bumpTxId) {
254
121
  throw new ErrorWrapper.EthLikeError({
255
122
  message: err.message,
256
123
  reason: ErrorWrapper.reasons.bumpTxFailed,
@@ -262,18 +129,23 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
262
129
  reason: ErrorWrapper.reasons.broadcastTxFailed,
263
130
  hint: 'otherErr:broadcastTx',
264
131
  })
265
- } else if (nonceTooLowErr && !isHardware) {
132
+ } else if (nonceTooLowErr && !unsignedTx.txMeta.isHardware) {
266
133
  console.info('trying to send again...') // inject logger factory from platform
267
134
  // let's try to fix the nonce issue
268
- nonce = await resolveNonce({
135
+ const newNonce = await asset.baseAsset.getNonce({
269
136
  asset,
270
137
  fromAddress,
271
- providedNonce,
272
- txLog: baseAssetTxLog,
138
+ walletAccount,
273
139
  triedNonce: nonce,
274
140
  forceFromNode: true,
275
141
  })
276
- ;({ txId, rawTx } = await createTx({ ...createTxParams, nonce }))
142
+
143
+ unsignedTx.txData.transactionBuffer = updateNonce(
144
+ unsignedTx.txData.transactionBuffer,
145
+ newNonce
146
+ )
147
+ nonce = newNonce
148
+ ;({ txId, rawTx } = await signTx({ asset, unsignedTx, walletAccount }))
277
149
 
278
150
  try {
279
151
  await baseAsset.api.broadcastTx(rawTx.toString('hex'))
@@ -296,7 +168,18 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
296
168
  }
297
169
  }
298
170
 
299
- const selfSend = fromAddress === address
171
+ const txData = eip1559Enabled
172
+ ? {
173
+ gasLimit,
174
+ replacedTxId,
175
+ nonce,
176
+ ...(tipGasPrice ? { tipGasPrice: tipGasPrice.toBaseString() } : Object.create(null)),
177
+ }
178
+ : {
179
+ gasLimit,
180
+ replacedTxId,
181
+ nonce,
182
+ }
300
183
 
301
184
  await assetClientInterface.updateTxLogAndNotify({
302
185
  assetName: asset.name,
@@ -310,21 +193,12 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
310
193
  feeAmount,
311
194
  feeCoinName: asset.feeAsset.name,
312
195
  selfSend,
313
- to: address,
196
+ to,
314
197
  currencies: {
315
198
  [assetName]: asset.currency,
316
199
  [asset.feeAsset.name]: asset.feeAsset.currency,
317
200
  },
318
- data: eip1559Enabled
319
- ? {
320
- gasLimit,
321
- replacedTxId: bumpTxId,
322
- nonce,
323
- ...(tipGasPrice
324
- ? { tipGasPrice: tipGasPrice.toBaseString() }
325
- : Object.create(null)),
326
- }
327
- : { gasLimit, replacedTxId: bumpTxId, nonce },
201
+ data: txData,
328
202
  },
329
203
  ],
330
204
  })
@@ -342,20 +216,13 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
342
216
  feeAmount,
343
217
  feeCoinName: baseAsset.name,
344
218
  selfSend,
345
- to: address,
219
+ to,
346
220
  token: asset.name,
347
221
  currencies: {
348
222
  [baseAsset.name]: baseAsset.currency,
349
223
  [asset.feeAsset.name]: asset.feeAsset.currency,
350
224
  },
351
- data: eip1559Enabled
352
- ? {
353
- gasLimit,
354
- replacedTxId: bumpTxId,
355
- nonce,
356
- ...(tipGasPrice ? { tipGasPrice: tipGasPrice.toBaseString() } : {}),
357
- }
358
- : { gasLimit, replacedTxId: bumpTxId, nonce },
225
+ data: txData,
359
226
  },
360
227
  ],
361
228
  })
@@ -365,146 +232,4 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
365
232
  }
366
233
  }
367
234
 
368
- const createTx = async ({
369
- assetClientInterface,
370
- asset,
371
- walletAccount,
372
- toAddress,
373
- amount,
374
- nonce,
375
- txInput,
376
- keepTxInput = false,
377
- customFee,
378
- isSendAll,
379
- fromAddress,
380
- feeOpts,
381
- createUnsignedTx,
382
- feeData,
383
- providedFeeAmount,
384
- }) => {
385
- assert(
386
- nonce !== undefined && typeof nonce === 'number',
387
- 'Nonce must be provided when creating a tx'
388
- )
389
- const isToken = isEthereumLikeToken(asset)
390
-
391
- if (txInput && isToken && !keepTxInput)
392
- throw new Error(`Additional data for Ethereum Token (${asset.name}) is not allowed`)
393
-
394
- txInput =
395
- isToken && !keepTxInput
396
- ? asset.contract.transfer.build(toAddress.toLowerCase(), amount.toBaseString())
397
- : txInput
398
-
399
- let { gasLimit, gasPrice, tipGasPrice, eip1559Enabled } = await getFeeInfo({
400
- assetClientInterface,
401
- asset,
402
- fromAddress,
403
- toAddress,
404
- amount,
405
- txInput,
406
- feeOpts,
407
- feeData,
408
- customFee,
409
- })
410
-
411
- const isContractToAddress = await isContractAddressCached({ asset, address: toAddress })
412
-
413
- // HACK: We cannot ensure the no dust invariant for `isSendAll`
414
- // transactions to contract addresses, since we may be
415
- // performing a raw token transaction and the parameter
416
- // applies to the token and not the native amount.
417
- //
418
- // Contracts have nondeterministic gas most of the time
419
- // versus estimations, anyway.
420
- const isSendAllBaseAsset = isSendAll && !isToken && !isContractToAddress
421
-
422
- // For native send all transactions, we have to make sure that
423
- // the `tipGasPrice` is equal to the `gasPrice`, since this is
424
- // effectively like saying that the `maxFeePerGas` is equal
425
- // to the `maxPriorityFeePerGas`. We do this so that for a
426
- // fixed gas cost transaction, no dust balance should remain,
427
- // since any deviation in the underlying `baseFeePerGas` will
428
- // result only affect the tip for the miner - no dust remains.
429
- if (eip1559Enabled && isSendAllBaseAsset) {
430
- // force consuming all gas
431
- tipGasPrice = gasPrice
432
- }
433
-
434
- // HACK: If we are handling a send all transaction, we must ensure
435
- // the send all invariant is maintained before producing the
436
- // final transaction.
437
- const maybeOverrideSendAllAmount =
438
- isSendAllBaseAsset &&
439
- (await HACK_maybeRefineSendAllAmount({
440
- amount,
441
- asset,
442
- assetClientInterface,
443
- walletAccount,
444
- gasLimit,
445
- gasPrice,
446
- }))
447
-
448
- if (maybeOverrideSendAllAmount) {
449
- console.log(
450
- `Attempted to execute a sendAll transaction with an amount of ${amount.toDefaultString({ unit: true })}, but this would fail to maintain the no dust invariant! Overriding with ${maybeOverrideSendAllAmount.toDefaultString({ unit: true })}.`
451
- )
452
- amount = maybeOverrideSendAllAmount
453
- }
454
-
455
- const unsignedTx = await createUnsignedTx({
456
- asset,
457
- walletAccount,
458
- address: toAddress,
459
- amount,
460
- nonce,
461
- txInput,
462
- gasLimit,
463
- gasPrice,
464
- tipGasPrice,
465
- fromAddress,
466
- eip1559Enabled,
467
- })
468
-
469
- // TODO: move into createUnsignedTx()
470
- if (keepTxInput && !isToken) {
471
- unsignedTx.txData.to = toAddress
472
- }
473
-
474
- unsignedTx.txMeta.eip1559Enabled = eip1559Enabled
475
-
476
- const resolveFee = async () => {
477
- if (providedFeeAmount) {
478
- return providedFeeAmount
479
- }
480
-
481
- const optimismL1DataFee = asset.baseAsset.estimateL1DataFee
482
- ? await asset.baseAsset.estimateL1DataFee({
483
- unsignedTx,
484
- })
485
- : undefined
486
-
487
- const l1DataFee = optimismL1DataFee
488
- ? asset.baseAsset.currency.baseUnit(optimismL1DataFee)
489
- : asset.baseAsset.currency.ZERO
490
- return gasPrice.mul(gasLimit).add(l1DataFee)
491
- }
492
-
493
- const { txId, rawTx } = await assetClientInterface.signTransaction({
494
- assetName: asset.baseAsset.name,
495
- unsignedTx,
496
- walletAccount,
497
- })
498
-
499
- return {
500
- txId: normalizeTxId(txId),
501
- rawTx,
502
- nonce,
503
- gasLimit,
504
- gasPrice,
505
- tipGasPrice,
506
- feeAmount: await resolveFee(),
507
- }
508
- }
509
-
510
235
  export default txSendFactory
@@ -1,58 +0,0 @@
1
- import { ensureSaneEip1559GasPriceForTipGasPrice } from '../fee-utils.js'
2
- import { fetchGasLimit } from '../gas-estimation.js'
3
- import { getFeeFactoryGasPrices } from '../get-fee.js'
4
-
5
- const getFeeInfo = async function getFeeInfo({
6
- assetClientInterface,
7
- asset,
8
- fromAddress,
9
- toAddress,
10
- amount,
11
- txInput,
12
- feeOpts = {},
13
- feeData,
14
- customFee,
15
- }) {
16
- // HACK: Previously, calls `getFeeInfo` were not provided a reference
17
- // to `feeData`. For backwards compatibility, we'll revert to
18
- // legacy behaviour.
19
- // NOTE: This shouldn't actually be used outside of the `assets` repo;
20
- // this is done just for safety.
21
- if (!feeData) {
22
- console.warn('`getFeeInfo` was not explicitly passed a `feeData` object.')
23
- const { name: assetName } = asset
24
- feeData = await assetClientInterface.getFeeData({ assetName })
25
- }
26
-
27
- const {
28
- gasPrice: gasPrice_,
29
- feeData: { tipGasPrice: tipGasPrice_, eip1559Enabled },
30
- } = getFeeFactoryGasPrices({ customFee, feeData })
31
-
32
- const tipGasPrice = feeOpts.tipGasPrice || tipGasPrice_
33
-
34
- const maybeGasPrice = feeOpts.gasPrice || gasPrice_
35
-
36
- // HACK: If we've received an invalid combination of `tipGasPrice`
37
- // (maxPriorityFeePerGas) and `gasPrice` (maxFeePerGas), then
38
- // we must normalize these before returning.
39
- const gasPrice = eip1559Enabled
40
- ? ensureSaneEip1559GasPriceForTipGasPrice({ gasPrice: maybeGasPrice, tipGasPrice })
41
- : maybeGasPrice
42
-
43
- const gasLimit =
44
- feeOpts.gasLimit ||
45
- (await fetchGasLimit({
46
- asset,
47
- fromAddress,
48
- toAddress,
49
- amount,
50
- txInput,
51
- feeData,
52
- throwOnError: false,
53
- }))
54
-
55
- return { gasPrice, gasLimit, tipGasPrice, eip1559Enabled }
56
- }
57
-
58
- export default getFeeInfo