@exodus/ethereum-api 8.34.6 → 8.34.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.
@@ -70,7 +70,7 @@ export const HACK_maybeRefineSendAllAmount = async ({
70
70
  const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
71
71
  assert(assetClientInterface, 'assetClientInterface is required')
72
72
  assert(createUnsignedTx, 'createUnsignedTx is required')
73
- return async ({ asset, walletAccount, address, amount, options = {} }) => {
73
+ return async ({ asset, walletAccount, address, amount, feeData: maybeFeeData, options = {} }) => {
74
74
  const {
75
75
  nft,
76
76
  bumpTxId,
@@ -93,13 +93,18 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
93
93
 
94
94
  const assets = await assetClientInterface.getAssetsForNetwork({ baseAssetName: baseAsset.name })
95
95
 
96
+ const feeData =
97
+ maybeFeeData ||
98
+ (await assetClientInterface.getFeeData({
99
+ assetName: baseAsset.name,
100
+ }))
101
+
102
+ const { eip1559Enabled } = feeData
103
+
96
104
  const fromAddress = await assetClientInterface.getReceiveAddress({
97
105
  assetName: baseAsset.name,
98
106
  walletAccount,
99
107
  })
100
- const feeData = await assetClientInterface.getFeeData({
101
- assetName: baseAsset.name,
102
- })
103
108
 
104
109
  let contractAddress
105
110
  if (nft) {
@@ -112,8 +117,6 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
112
117
 
113
118
  let bumpNonce
114
119
 
115
- let eip1559Enabled = feeData.eip1559Enabled
116
-
117
120
  const baseAssetTxLog = await assetClientInterface.getTxLog({
118
121
  assetName: baseAsset.name,
119
122
  walletAccount,
@@ -123,36 +126,47 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
123
126
  let replacedTx, replacedTokenTx
124
127
  if (bumpTxId) {
125
128
  replacedTx = baseAssetTxLog.get(bumpTxId)
129
+
126
130
  if (!replacedTx || !replacedTx.pending) {
127
131
  throw new Error(`Cannot bump transaction ${bumpTxId}: not found or confirmed`)
128
132
  }
129
133
 
130
134
  if (replacedTx.tokens.length > 0) {
135
+ const [tokenAssetName] = replacedTx.tokens
131
136
  const tokenTxSet = await assetClientInterface.getTxLog({
132
- assetName: replacedTx.tokens[0],
137
+ assetName: tokenAssetName,
133
138
  walletAccount,
134
139
  })
135
140
  replacedTokenTx = tokenTxSet.get(bumpTxId)
136
141
 
137
142
  if (replacedTokenTx) {
138
- asset = assets[replacedTx.tokens[0]]
143
+ // Attempt to overwrite the asset to reflect the fact that
144
+ // we're performing a token transaction.
145
+ asset = assets[tokenAssetName]
146
+ if (!asset) {
147
+ console.warn(
148
+ `unable to find ${tokenAssetName} during token bump transaction: asset was not available in assetsForNetwork`
149
+ )
150
+ }
139
151
  }
152
+
153
+ // TODO: Should we `throw` if we can't find the asset?
140
154
  }
141
155
 
142
156
  address = (replacedTokenTx || replacedTx).to
143
157
  amount = (replacedTokenTx || replacedTx).coinAmount.negate()
144
158
  feeOpts.gasLimit = replacedTx.data.gasLimit
145
159
 
146
- const { gasPrice: currentGasPrice } = feeData
160
+ const { gasPrice: currentGasPrice, baseFeePerGas: currentBaseFee } = feeData
147
161
  const { bumpedGasPrice, bumpedTipGasPrice } = calculateBumpedGasPrice({
148
162
  baseAsset,
149
163
  tx: replacedTx,
150
164
  currentGasPrice,
165
+ currentBaseFee,
151
166
  eip1559Enabled,
152
167
  })
153
168
  feeOpts.gasPrice = bumpedGasPrice
154
169
  feeOpts.tipGasPrice = bumpedTipGasPrice
155
- eip1559Enabled = feeData.eip1559Enabled && feeOpts.tipGasPrice
156
170
  bumpNonce = replacedTx.data.nonce
157
171
  txInput = replacedTokenTx ? null : replacedTx.data.data || '0x'
158
172
  if (bumpNonce === undefined) {
@@ -160,9 +174,40 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
160
174
  }
161
175
  }
162
176
 
177
+ // If we have evaluated a bump transaction and the `providedNonce` differs
178
+ // from the `bumpNonce`, we've encountered a conflict and cannot respect
179
+ // the caller's request.
180
+ if (
181
+ typeof bumpNonce === 'number' &&
182
+ typeof providedNonce === 'number' &&
183
+ bumpNonce !== providedNonce
184
+ )
185
+ throw new ErrorWrapper.EthLikeError({
186
+ message: new Error('incorrect nonce for replacement transaction'),
187
+ reason: ErrorWrapper.reasons.bumpTxFailed,
188
+ hint: 'providedNonce',
189
+ })
190
+
191
+ // Choose a nonce that compensates for transctions which are currently
192
+ // in pending; for example, when we send transactions at low gas which
193
+ // will be stored by `geth` for execution at a later point in time.
194
+ //
195
+ // When we are not intentionally bumping a transaction, users are
196
+ // appending a new transaction to the chain - therefore we should
197
+ // be mindful of nonces belonging to us which are currently pending.
163
198
  const resolvedNonce =
199
+ providedNonce ??
164
200
  bumpNonce ??
165
- (await resolveNonce({ asset, fromAddress, providedNonce, txLog: baseAssetTxLog }))
201
+ (await resolveNonce({
202
+ asset,
203
+ fromAddress,
204
+ txLog: baseAssetTxLog,
205
+ // For assets where we'll fall back to querying the coin node, we
206
+ // search for pending transactions. For base assets with history,
207
+ // we'll fall back to the `TxLog` since this also has a knowledge
208
+ // of which transactions are currently in pending.
209
+ tag: 'pending',
210
+ }))
166
211
 
167
212
  const createTxParams = {
168
213
  assetClientInterface,
@@ -172,14 +217,15 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
172
217
  amount,
173
218
  nonce: resolvedNonce,
174
219
  fromAddress,
175
- eip1559Enabled,
176
220
  customFee,
177
221
  feeOpts,
178
222
  txInput,
179
223
  keepTxInput,
180
224
  isSendAll,
181
225
  createUnsignedTx,
226
+ feeData,
182
227
  }
228
+
183
229
  let { txId, rawTx, nonce, gasLimit, tipGasPrice, gasPrice } = await createTx(createTxParams)
184
230
 
185
231
  const feeAmount = gasPrice.mul(gasLimit)
@@ -324,13 +370,13 @@ const createTx = async ({
324
370
  amount,
325
371
  nonce,
326
372
  txInput,
327
- eip1559Enabled = true,
328
373
  keepTxInput = false,
329
- customFee: customGasPrice,
374
+ customFee,
330
375
  isSendAll,
331
376
  fromAddress,
332
377
  feeOpts,
333
378
  createUnsignedTx,
379
+ feeData,
334
380
  }) => {
335
381
  assert(
336
382
  nonce !== undefined && typeof nonce === 'number',
@@ -346,7 +392,7 @@ const createTx = async ({
346
392
  ? asset.contract.transfer.build(toAddress.toLowerCase(), amount.toBaseString())
347
393
  : txInput
348
394
 
349
- let { gasLimit, gasPrice, tipGasPrice } = await getFeeInfo({
395
+ let { gasLimit, gasPrice, tipGasPrice, eip1559Enabled } = await getFeeInfo({
350
396
  assetClientInterface,
351
397
  asset,
352
398
  fromAddress,
@@ -354,6 +400,8 @@ const createTx = async ({
354
400
  amount,
355
401
  txInput,
356
402
  feeOpts,
403
+ feeData,
404
+ customFee,
357
405
  })
358
406
 
359
407
  const isContractToAddress = await isContractAddressCached({ asset, address: toAddress })
@@ -367,22 +415,16 @@ const createTx = async ({
367
415
  // versus estimations, anyway.
368
416
  const isSendAllBaseAsset = isSendAll && !isToken && !isContractToAddress
369
417
 
370
- if (eip1559Enabled) {
371
- if (customGasPrice) {
372
- gasPrice = customGasPrice // aka maxFeePerGas
373
-
374
- // We must ensure maxPriorityFeePerGas <= maxFeePerGas or our transaction library throws an error
375
- // It's a bit counterintuitive since maxPriorityFeePerGas should only contain the tip,
376
- // so we should be subtracting the base gas price from the custom gas price to keep just the tip
377
- // but the fee is also limited by our maxFeePerGas above, so that implicitly captures the max tip.
378
- // Setting this tipGasPrice to undefined will result in a legacy transaction (not an EIP1559 anymore)
379
- tipGasPrice = customGasPrice // aka maxPriorityFeePerGas
380
- }
381
-
382
- if (isSendAllBaseAsset) {
383
- // force consuming all gas
384
- tipGasPrice = gasPrice
385
- }
418
+ // For native send all transactions, we have to make sure that
419
+ // the `tipGasPrice` is equal to the `gasPrice`, since this is
420
+ // effectively like saying that the `maxFeePerGas` is equal
421
+ // to the `maxPriorityFeePerGas`. We do this so that for a
422
+ // fixed gas cost transaction, no dust balance should remain,
423
+ // since any deviation in the underlying `baseFeePerGas` will
424
+ // result only affect the tip for the miner - no dust remains.
425
+ if (eip1559Enabled && isSendAllBaseAsset) {
426
+ // force consuming all gas
427
+ tipGasPrice = gasPrice
386
428
  }
387
429
 
388
430
  // HACK: If we are handling a send all transaction, we must ensure
@@ -1,2 +0,0 @@
1
- /* global WebSocket */
2
- export default WebSocket
@@ -1,2 +0,0 @@
1
- /* global WebSocket */
2
- export default WebSocket
@@ -1,2 +0,0 @@
1
- // Uses cjs to optimize out requiring / bundling ws
2
- export { default } from './websocket.cjs'
@@ -1,21 +0,0 @@
1
- /**
2
- * This is a "light" version of @exodus/core resolution to select the native WebSocket on BE
3
- * It's a workaround until https://github.com/ExodusMovement/assets/pull/72 is merged.
4
- *
5
- * Based on https://github.com/ExodusMovement/fetch/blob/master/core.js , note that chooses native on Desktop
6
- */
7
- if (typeof process !== 'undefined' && (process.type === 'renderer' || process.type === 'worker')) {
8
- // THIS IS FOR DESKTOP
9
- if (process.env.EXODUS_DISABLE_WS) {
10
- module.exports = globalThis.WebSocket
11
- } else {
12
- module.exports = require('ws')
13
- }
14
- // eslint-disable-next-line no-undef
15
- } else if (typeof WebSocket === 'undefined') {
16
- // THIS IS FOR UNIT TESTING.
17
- module.exports = require('ws')
18
- } else {
19
- // THIS IS FOR BE
20
- module.exports = globalThis.WebSocket
21
- }