@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.
- package/CHANGELOG.md +20 -0
- package/package.json +4 -5
- package/src/create-asset.js +1 -2
- package/src/custom-fees.js +40 -10
- package/src/etherscan/ws.js +1 -2
- package/src/exodus-eth-server/ws.js +1 -2
- package/src/fee-utils.js +17 -3
- package/src/gas-estimation.js +25 -6
- package/src/get-fee.js +29 -35
- package/src/index.js +2 -0
- package/src/staking/ethereum/api.js +1 -0
- package/src/staking/ethereum/service.js +116 -92
- package/src/staking/matic/matic-staking.md +53 -0
- package/src/staking/matic/service.js +189 -102
- package/src/staking/utils/index.js +26 -60
- package/src/tx-send/get-fee-info.js +34 -9
- package/src/tx-send/nonce-utils.js +2 -1
- package/src/tx-send/tx-send.js +73 -31
- package/src/websocket/index.android.js +0 -2
- package/src/websocket/index.ios.js +0 -2
- package/src/websocket/index.js +0 -2
- package/src/websocket/websocket.cjs +0 -21
package/src/tx-send/tx-send.js
CHANGED
|
@@ -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:
|
|
137
|
+
assetName: tokenAssetName,
|
|
133
138
|
walletAccount,
|
|
134
139
|
})
|
|
135
140
|
replacedTokenTx = tokenTxSet.get(bumpTxId)
|
|
136
141
|
|
|
137
142
|
if (replacedTokenTx) {
|
|
138
|
-
asset
|
|
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({
|
|
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
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
package/src/websocket/index.js
DELETED
|
@@ -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
|
-
}
|