@exodus/ethereum-api 8.34.5 → 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.
@@ -29,7 +29,13 @@ export const HACK_maybeRefineSendAllAmount = async ({
29
29
  gasPrice,
30
30
  }) => {
31
31
  try {
32
- const { name: assetName } = asset
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
33
39
 
34
40
  const [txLog, accountState] = await Promise.all([
35
41
  assetClientInterface.getTxLog({
@@ -64,7 +70,7 @@ export const HACK_maybeRefineSendAllAmount = async ({
64
70
  const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
65
71
  assert(assetClientInterface, 'assetClientInterface is required')
66
72
  assert(createUnsignedTx, 'createUnsignedTx is required')
67
- return async ({ asset, walletAccount, address, amount, options = {} }) => {
73
+ return async ({ asset, walletAccount, address, amount, feeData: maybeFeeData, options = {} }) => {
68
74
  const {
69
75
  nft,
70
76
  bumpTxId,
@@ -87,13 +93,18 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
87
93
 
88
94
  const assets = await assetClientInterface.getAssetsForNetwork({ baseAssetName: baseAsset.name })
89
95
 
96
+ const feeData =
97
+ maybeFeeData ||
98
+ (await assetClientInterface.getFeeData({
99
+ assetName: baseAsset.name,
100
+ }))
101
+
102
+ const { eip1559Enabled } = feeData
103
+
90
104
  const fromAddress = await assetClientInterface.getReceiveAddress({
91
105
  assetName: baseAsset.name,
92
106
  walletAccount,
93
107
  })
94
- const feeData = await assetClientInterface.getFeeData({
95
- assetName: baseAsset.name,
96
- })
97
108
 
98
109
  let contractAddress
99
110
  if (nft) {
@@ -106,8 +117,6 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
106
117
 
107
118
  let bumpNonce
108
119
 
109
- let eip1559Enabled = feeData.eip1559Enabled
110
-
111
120
  const baseAssetTxLog = await assetClientInterface.getTxLog({
112
121
  assetName: baseAsset.name,
113
122
  walletAccount,
@@ -117,36 +126,47 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
117
126
  let replacedTx, replacedTokenTx
118
127
  if (bumpTxId) {
119
128
  replacedTx = baseAssetTxLog.get(bumpTxId)
129
+
120
130
  if (!replacedTx || !replacedTx.pending) {
121
131
  throw new Error(`Cannot bump transaction ${bumpTxId}: not found or confirmed`)
122
132
  }
123
133
 
124
134
  if (replacedTx.tokens.length > 0) {
135
+ const [tokenAssetName] = replacedTx.tokens
125
136
  const tokenTxSet = await assetClientInterface.getTxLog({
126
- assetName: replacedTx.tokens[0],
137
+ assetName: tokenAssetName,
127
138
  walletAccount,
128
139
  })
129
140
  replacedTokenTx = tokenTxSet.get(bumpTxId)
130
141
 
131
142
  if (replacedTokenTx) {
132
- 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
+ }
133
151
  }
152
+
153
+ // TODO: Should we `throw` if we can't find the asset?
134
154
  }
135
155
 
136
156
  address = (replacedTokenTx || replacedTx).to
137
157
  amount = (replacedTokenTx || replacedTx).coinAmount.negate()
138
158
  feeOpts.gasLimit = replacedTx.data.gasLimit
139
159
 
140
- const { gasPrice: currentGasPrice } = feeData
160
+ const { gasPrice: currentGasPrice, baseFeePerGas: currentBaseFee } = feeData
141
161
  const { bumpedGasPrice, bumpedTipGasPrice } = calculateBumpedGasPrice({
142
162
  baseAsset,
143
163
  tx: replacedTx,
144
164
  currentGasPrice,
165
+ currentBaseFee,
145
166
  eip1559Enabled,
146
167
  })
147
168
  feeOpts.gasPrice = bumpedGasPrice
148
169
  feeOpts.tipGasPrice = bumpedTipGasPrice
149
- eip1559Enabled = feeData.eip1559Enabled && feeOpts.tipGasPrice
150
170
  bumpNonce = replacedTx.data.nonce
151
171
  txInput = replacedTokenTx ? null : replacedTx.data.data || '0x'
152
172
  if (bumpNonce === undefined) {
@@ -154,9 +174,40 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
154
174
  }
155
175
  }
156
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.
157
198
  const resolvedNonce =
199
+ providedNonce ??
158
200
  bumpNonce ??
159
- (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
+ }))
160
211
 
161
212
  const createTxParams = {
162
213
  assetClientInterface,
@@ -166,14 +217,15 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
166
217
  amount,
167
218
  nonce: resolvedNonce,
168
219
  fromAddress,
169
- eip1559Enabled,
170
220
  customFee,
171
221
  feeOpts,
172
222
  txInput,
173
223
  keepTxInput,
174
224
  isSendAll,
175
225
  createUnsignedTx,
226
+ feeData,
176
227
  }
228
+
177
229
  let { txId, rawTx, nonce, gasLimit, tipGasPrice, gasPrice } = await createTx(createTxParams)
178
230
 
179
231
  const feeAmount = gasPrice.mul(gasLimit)
@@ -318,13 +370,13 @@ const createTx = async ({
318
370
  amount,
319
371
  nonce,
320
372
  txInput,
321
- eip1559Enabled = true,
322
373
  keepTxInput = false,
323
- customFee: customGasPrice,
374
+ customFee,
324
375
  isSendAll,
325
376
  fromAddress,
326
377
  feeOpts,
327
378
  createUnsignedTx,
379
+ feeData,
328
380
  }) => {
329
381
  assert(
330
382
  nonce !== undefined && typeof nonce === 'number',
@@ -340,7 +392,7 @@ const createTx = async ({
340
392
  ? asset.contract.transfer.build(toAddress.toLowerCase(), amount.toBaseString())
341
393
  : txInput
342
394
 
343
- let { gasLimit, gasPrice, tipGasPrice } = await getFeeInfo({
395
+ let { gasLimit, gasPrice, tipGasPrice, eip1559Enabled } = await getFeeInfo({
344
396
  assetClientInterface,
345
397
  asset,
346
398
  fromAddress,
@@ -348,6 +400,8 @@ const createTx = async ({
348
400
  amount,
349
401
  txInput,
350
402
  feeOpts,
403
+ feeData,
404
+ customFee,
351
405
  })
352
406
 
353
407
  const isContractToAddress = await isContractAddressCached({ asset, address: toAddress })
@@ -361,22 +415,16 @@ const createTx = async ({
361
415
  // versus estimations, anyway.
362
416
  const isSendAllBaseAsset = isSendAll && !isToken && !isContractToAddress
363
417
 
364
- if (eip1559Enabled) {
365
- if (customGasPrice) {
366
- gasPrice = customGasPrice // aka maxFeePerGas
367
-
368
- // We must ensure maxPriorityFeePerGas <= maxFeePerGas or our transaction library throws an error
369
- // It's a bit counterintuitive since maxPriorityFeePerGas should only contain the tip,
370
- // so we should be subtracting the base gas price from the custom gas price to keep just the tip
371
- // but the fee is also limited by our maxFeePerGas above, so that implicitly captures the max tip.
372
- // Setting this tipGasPrice to undefined will result in a legacy transaction (not an EIP1559 anymore)
373
- tipGasPrice = customGasPrice // aka maxPriorityFeePerGas
374
- }
375
-
376
- if (isSendAllBaseAsset) {
377
- // force consuming all gas
378
- tipGasPrice = gasPrice
379
- }
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
380
428
  }
381
429
 
382
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
- }