@exodus/ethereum-api 8.40.0 → 8.41.0-alpha.0
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 +28 -0
- package/package.json +4 -5
- package/src/create-asset-plugin-factory.js +0 -6
- package/src/create-asset-utils.js +19 -7
- package/src/create-asset.js +22 -12
- package/src/gas-estimation.js +11 -10
- package/src/get-balances.js +2 -9
- package/src/get-fee-async.js +13 -81
- package/src/get-fee.js +1 -1
- package/src/index.js +1 -3
- package/src/staking/ethereum/service.js +6 -6
- package/src/staking/matic/service.js +7 -7
- package/src/tx-create.js +352 -0
- package/src/tx-log/clarity-monitor.js +6 -5
- package/src/tx-log/ethereum-monitor.js +5 -4
- package/src/tx-log/monitor-utils/get-fee-amount.js +4 -1
- package/src/tx-send/index.js +0 -1
- package/src/tx-send/nonce-utils.js +1 -1
- package/src/tx-send/tx-send.js +84 -376
- package/src/tx-send/get-fee-info.js +0 -58
package/src/tx-send/tx-send.js
CHANGED
|
@@ -1,239 +1,96 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { calculateBumpedGasPrice, isEthereumLikeToken, normalizeTxId } from '@exodus/ethereum-lib'
|
|
1
|
+
import { isEthereumLikeToken, normalizeTxId, parseUnsignedTx } from '@exodus/ethereum-lib'
|
|
4
2
|
import assert from 'minimalistic-assert'
|
|
5
3
|
|
|
6
4
|
import * as ErrorWrapper from '../error-wrapper.js'
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import getFeeInfo from './get-fee-info.js'
|
|
5
|
+
import { transactionExists } from '../eth-like-util.js'
|
|
6
|
+
import { ARBITRARY_ADDRESS } from '../gas-estimation.js'
|
|
10
7
|
import { resolveNonce } from './nonce-utils.js'
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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')
|
|
55
|
-
|
|
56
|
-
const expectedSendAllAmount = spendable.sub(maxGasCost)
|
|
9
|
+
const txSendFactory = ({ assetClientInterface, createTx }) => {
|
|
10
|
+
assert(assetClientInterface, 'assetClientInterface is required')
|
|
11
|
+
assert(createTx, 'createTx is required')
|
|
57
12
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
13
|
+
async function signTx({ asset, unsignedTx, walletAccount }) {
|
|
14
|
+
const { rawTx, txId } = await assetClientInterface.signTransaction({
|
|
15
|
+
assetName: asset.baseAsset.name,
|
|
16
|
+
unsignedTx,
|
|
17
|
+
walletAccount,
|
|
18
|
+
})
|
|
61
19
|
|
|
62
|
-
|
|
63
|
-
return expectedSendAllAmount
|
|
64
|
-
} catch (e) {
|
|
65
|
-
console.error('failed to refine send all amount', e)
|
|
66
|
-
return null
|
|
20
|
+
return { rawTx, txId: normalizeTxId(txId) }
|
|
67
21
|
}
|
|
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
22
|
|
|
23
|
+
return async ({ asset, walletAccount, unsignedTx: providedUnsignedTx, ...legacyParams }) => {
|
|
92
24
|
const assetName = asset.name
|
|
93
25
|
const baseAsset = asset.baseAsset
|
|
94
26
|
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
(await assetClientInterface.getFeeData({
|
|
100
|
-
assetName: baseAsset.name,
|
|
101
|
-
}))
|
|
27
|
+
const resolveUnsignedTx = async () => {
|
|
28
|
+
if (providedUnsignedTx) {
|
|
29
|
+
return { unsignedTx: providedUnsignedTx }
|
|
30
|
+
}
|
|
102
31
|
|
|
103
|
-
|
|
32
|
+
const feeData =
|
|
33
|
+
legacyParams.feeData ??
|
|
34
|
+
(await assetClientInterface.getFeeData({
|
|
35
|
+
assetName: baseAsset.name,
|
|
36
|
+
}))
|
|
104
37
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
38
|
+
const fromAddress =
|
|
39
|
+
legacyParams.fromAddress ??
|
|
40
|
+
(await assetClientInterface.getReceiveAddress({
|
|
41
|
+
assetName: baseAsset.name,
|
|
42
|
+
walletAccount,
|
|
43
|
+
}))
|
|
109
44
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
45
|
+
return createTx({
|
|
46
|
+
asset,
|
|
47
|
+
walletAccount,
|
|
48
|
+
feeData,
|
|
49
|
+
fromAddress,
|
|
50
|
+
toAddress: legacyParams.address,
|
|
51
|
+
...legacyParams,
|
|
52
|
+
...legacyParams.options,
|
|
53
|
+
})
|
|
117
54
|
}
|
|
118
55
|
|
|
119
|
-
|
|
56
|
+
const { unsignedTx } = await resolveUnsignedTx()
|
|
120
57
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
walletAccount,
|
|
124
|
-
})
|
|
58
|
+
// this converts an transactionBuffer to values we can use when creating the tx logs
|
|
59
|
+
const parsedTx = parseUnsignedTx({ asset, unsignedTx })
|
|
125
60
|
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
61
|
+
// the txMeta.fee may include implicit l1 fees
|
|
62
|
+
const feeAmount = unsignedTx.txMeta.fee
|
|
63
|
+
? asset.feeAsset.currency.parse(unsignedTx.txMeta.fee)
|
|
64
|
+
: parsedTx.fee
|
|
130
65
|
|
|
131
|
-
|
|
132
|
-
throw new Error(`Cannot bump transaction ${bumpTxId}: not found or confirmed`)
|
|
133
|
-
}
|
|
66
|
+
let nonce = parsedTx.nonce
|
|
134
67
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
})
|
|
141
|
-
replacedTokenTx = tokenTxSet.get(bumpTxId)
|
|
142
|
-
|
|
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
|
-
}
|
|
68
|
+
const tipGasPrice = parsedTx.tipGasPrice
|
|
69
|
+
const gasLimit = parsedTx.gasLimit
|
|
70
|
+
const amount = parsedTx.amount
|
|
71
|
+
const to = parsedTx.to
|
|
72
|
+
const eip1559Enabled = parsedTx.eip1559Enabled
|
|
153
73
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
address = (replacedTokenTx || replacedTx).to
|
|
158
|
-
amount = (replacedTokenTx || replacedTx).coinAmount.negate()
|
|
159
|
-
feeOpts.gasLimit = replacedTx.data.gasLimit
|
|
160
|
-
|
|
161
|
-
const { gasPrice: currentGasPrice, baseFeePerGas: currentBaseFee } = feeData
|
|
162
|
-
const { bumpedGasPrice, bumpedTipGasPrice } = calculateBumpedGasPrice({
|
|
163
|
-
baseAsset,
|
|
164
|
-
tx: replacedTx,
|
|
165
|
-
currentGasPrice,
|
|
166
|
-
currentBaseFee,
|
|
167
|
-
eip1559Enabled,
|
|
168
|
-
})
|
|
169
|
-
feeOpts.gasPrice = bumpedGasPrice
|
|
170
|
-
feeOpts.tipGasPrice = bumpedTipGasPrice
|
|
171
|
-
bumpNonce = replacedTx.data.nonce
|
|
172
|
-
txInput = replacedTokenTx ? null : replacedTx.data.data || '0x'
|
|
173
|
-
if (bumpNonce === undefined) {
|
|
174
|
-
throw new Error(`Cannot bump transaction ${bumpTxId}: data object seems to be corrupted`)
|
|
175
|
-
}
|
|
176
|
-
}
|
|
74
|
+
// unknown data from buffer...
|
|
75
|
+
const selfSend = unsignedTx.txMeta.fromAddress === to
|
|
76
|
+
const replacedTxId = unsignedTx.txMeta.bumpTxId
|
|
177
77
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (
|
|
182
|
-
typeof bumpNonce === 'number' &&
|
|
183
|
-
typeof providedNonce === 'number' &&
|
|
184
|
-
bumpNonce !== providedNonce
|
|
78
|
+
assert(
|
|
79
|
+
to.toLowerCase() !== ARBITRARY_ADDRESS,
|
|
80
|
+
`The receiving wallet address must not be ${ARBITRARY_ADDRESS}`
|
|
185
81
|
)
|
|
186
|
-
throw new ErrorWrapper.EthLikeError({
|
|
187
|
-
message: new Error('incorrect nonce for replacement transaction'),
|
|
188
|
-
reason: ErrorWrapper.reasons.bumpTxFailed,
|
|
189
|
-
hint: 'providedNonce',
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
// Choose a nonce that compensates for transctions which are currently
|
|
193
|
-
// in pending; for example, when we send transactions at low gas which
|
|
194
|
-
// will be stored by `geth` for execution at a later point in time.
|
|
195
|
-
//
|
|
196
|
-
// When we are not intentionally bumping a transaction, users are
|
|
197
|
-
// appending a new transaction to the chain - therefore we should
|
|
198
|
-
// be mindful of nonces belonging to us which are currently pending.
|
|
199
|
-
const resolvedNonce =
|
|
200
|
-
providedNonce ??
|
|
201
|
-
bumpNonce ??
|
|
202
|
-
(await resolveNonce({
|
|
203
|
-
asset,
|
|
204
|
-
fromAddress,
|
|
205
|
-
txLog: baseAssetTxLog,
|
|
206
|
-
// For assets where we'll fall back to querying the coin node, we
|
|
207
|
-
// search for pending transactions. For base assets with history,
|
|
208
|
-
// we'll fall back to the `TxLog` since this also has a knowledge
|
|
209
|
-
// of which transactions are currently in pending.
|
|
210
|
-
tag: 'pending',
|
|
211
|
-
useAbsoluteNonce: useAbsoluteBalanceAndNonce,
|
|
212
|
-
}))
|
|
213
82
|
|
|
214
|
-
|
|
215
|
-
assetClientInterface,
|
|
83
|
+
let { txId, rawTx } = await signTx({
|
|
216
84
|
asset,
|
|
85
|
+
unsignedTx,
|
|
217
86
|
walletAccount,
|
|
218
|
-
|
|
219
|
-
amount,
|
|
220
|
-
nonce: resolvedNonce,
|
|
221
|
-
fromAddress,
|
|
222
|
-
customFee,
|
|
223
|
-
feeOpts,
|
|
224
|
-
txInput,
|
|
225
|
-
keepTxInput,
|
|
226
|
-
isSendAll,
|
|
227
|
-
createUnsignedTx,
|
|
228
|
-
feeData,
|
|
229
|
-
providedFeeAmount,
|
|
230
|
-
}
|
|
87
|
+
})
|
|
231
88
|
|
|
232
|
-
|
|
89
|
+
const isPrivate = Boolean(legacyParams?.options?.isPrivate)
|
|
233
90
|
|
|
234
|
-
if (isPrivate &&
|
|
91
|
+
if (isPrivate && typeof baseAsset.broadcastPrivateTx !== 'function')
|
|
235
92
|
throw new Error(
|
|
236
|
-
`unable to send private transaction -
|
|
93
|
+
`unable to send private transaction - private mempools are not enabled for ${baseAsset.name}`
|
|
237
94
|
)
|
|
238
95
|
|
|
239
96
|
const broadcastTx = isPrivate ? baseAsset.broadcastPrivateTx : baseAsset.api.broadcastTx
|
|
@@ -255,7 +112,7 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
|
|
|
255
112
|
reason: ErrorWrapper.reasons.insufficientFunds,
|
|
256
113
|
hint: 'broadcastTx',
|
|
257
114
|
})
|
|
258
|
-
} else if (bumpTxId) {
|
|
115
|
+
} else if (unsignedTx.txMeta.bumpTxId) {
|
|
259
116
|
throw new ErrorWrapper.EthLikeError({
|
|
260
117
|
message: err.message,
|
|
261
118
|
reason: ErrorWrapper.reasons.bumpTxFailed,
|
|
@@ -267,18 +124,16 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
|
|
|
267
124
|
reason: ErrorWrapper.reasons.broadcastTxFailed,
|
|
268
125
|
hint: 'otherErr:broadcastTx',
|
|
269
126
|
})
|
|
270
|
-
} else if (nonceTooLowErr && !isHardware) {
|
|
127
|
+
} else if (nonceTooLowErr && !unsignedTx.txMeta.isHardware) {
|
|
271
128
|
console.info('trying to send again...') // inject logger factory from platform
|
|
272
129
|
// let's try to fix the nonce issue
|
|
273
|
-
|
|
130
|
+
const newNonce = await resolveNonce({
|
|
274
131
|
asset,
|
|
275
|
-
fromAddress,
|
|
276
|
-
providedNonce,
|
|
277
|
-
txLog: baseAssetTxLog,
|
|
278
132
|
triedNonce: nonce,
|
|
279
133
|
forceFromNode: true,
|
|
280
134
|
})
|
|
281
|
-
|
|
135
|
+
nonce = newNonce
|
|
136
|
+
;({ txId, rawTx } = await signTx({ asset, unsignedTx, walletAccount }))
|
|
282
137
|
|
|
283
138
|
try {
|
|
284
139
|
await baseAsset.api.broadcastTx(rawTx.toString('hex'))
|
|
@@ -301,7 +156,18 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
|
|
|
301
156
|
}
|
|
302
157
|
}
|
|
303
158
|
|
|
304
|
-
const
|
|
159
|
+
const txData = eip1559Enabled
|
|
160
|
+
? {
|
|
161
|
+
gasLimit,
|
|
162
|
+
replacedTxId,
|
|
163
|
+
nonce,
|
|
164
|
+
...(tipGasPrice ? { tipGasPrice: tipGasPrice.toBaseString() } : Object.create(null)),
|
|
165
|
+
}
|
|
166
|
+
: {
|
|
167
|
+
gasLimit,
|
|
168
|
+
replacedTxId,
|
|
169
|
+
nonce,
|
|
170
|
+
}
|
|
305
171
|
|
|
306
172
|
await assetClientInterface.updateTxLogAndNotify({
|
|
307
173
|
assetName: asset.name,
|
|
@@ -315,21 +181,12 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
|
|
|
315
181
|
feeAmount,
|
|
316
182
|
feeCoinName: asset.feeAsset.name,
|
|
317
183
|
selfSend,
|
|
318
|
-
to
|
|
184
|
+
to,
|
|
319
185
|
currencies: {
|
|
320
186
|
[assetName]: asset.currency,
|
|
321
187
|
[asset.feeAsset.name]: asset.feeAsset.currency,
|
|
322
188
|
},
|
|
323
|
-
data:
|
|
324
|
-
? {
|
|
325
|
-
gasLimit,
|
|
326
|
-
replacedTxId: bumpTxId,
|
|
327
|
-
nonce,
|
|
328
|
-
...(tipGasPrice
|
|
329
|
-
? { tipGasPrice: tipGasPrice.toBaseString() }
|
|
330
|
-
: Object.create(null)),
|
|
331
|
-
}
|
|
332
|
-
: { gasLimit, replacedTxId: bumpTxId, nonce },
|
|
189
|
+
data: txData,
|
|
333
190
|
},
|
|
334
191
|
],
|
|
335
192
|
})
|
|
@@ -347,20 +204,13 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
|
|
|
347
204
|
feeAmount,
|
|
348
205
|
feeCoinName: baseAsset.name,
|
|
349
206
|
selfSend,
|
|
350
|
-
to
|
|
207
|
+
to,
|
|
351
208
|
token: asset.name,
|
|
352
209
|
currencies: {
|
|
353
210
|
[baseAsset.name]: baseAsset.currency,
|
|
354
211
|
[asset.feeAsset.name]: asset.feeAsset.currency,
|
|
355
212
|
},
|
|
356
|
-
data:
|
|
357
|
-
? {
|
|
358
|
-
gasLimit,
|
|
359
|
-
replacedTxId: bumpTxId,
|
|
360
|
-
nonce,
|
|
361
|
-
...(tipGasPrice ? { tipGasPrice: tipGasPrice.toBaseString() } : {}),
|
|
362
|
-
}
|
|
363
|
-
: { gasLimit, replacedTxId: bumpTxId, nonce },
|
|
213
|
+
data: txData,
|
|
364
214
|
},
|
|
365
215
|
],
|
|
366
216
|
})
|
|
@@ -370,146 +220,4 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
|
|
|
370
220
|
}
|
|
371
221
|
}
|
|
372
222
|
|
|
373
|
-
const createTx = async ({
|
|
374
|
-
assetClientInterface,
|
|
375
|
-
asset,
|
|
376
|
-
walletAccount,
|
|
377
|
-
toAddress,
|
|
378
|
-
amount,
|
|
379
|
-
nonce,
|
|
380
|
-
txInput,
|
|
381
|
-
keepTxInput = false,
|
|
382
|
-
customFee,
|
|
383
|
-
isSendAll,
|
|
384
|
-
fromAddress,
|
|
385
|
-
feeOpts,
|
|
386
|
-
createUnsignedTx,
|
|
387
|
-
feeData,
|
|
388
|
-
providedFeeAmount,
|
|
389
|
-
}) => {
|
|
390
|
-
assert(
|
|
391
|
-
nonce !== undefined && typeof nonce === 'number',
|
|
392
|
-
'Nonce must be provided when creating a tx'
|
|
393
|
-
)
|
|
394
|
-
const isToken = isEthereumLikeToken(asset)
|
|
395
|
-
|
|
396
|
-
if (txInput && isToken && !keepTxInput)
|
|
397
|
-
throw new Error(`Additional data for Ethereum Token (${asset.name}) is not allowed`)
|
|
398
|
-
|
|
399
|
-
txInput =
|
|
400
|
-
isToken && !keepTxInput
|
|
401
|
-
? asset.contract.transfer.build(toAddress.toLowerCase(), amount.toBaseString())
|
|
402
|
-
: txInput
|
|
403
|
-
|
|
404
|
-
let { gasLimit, gasPrice, tipGasPrice, eip1559Enabled } = await getFeeInfo({
|
|
405
|
-
assetClientInterface,
|
|
406
|
-
asset,
|
|
407
|
-
fromAddress,
|
|
408
|
-
toAddress,
|
|
409
|
-
amount,
|
|
410
|
-
txInput,
|
|
411
|
-
feeOpts,
|
|
412
|
-
feeData,
|
|
413
|
-
customFee,
|
|
414
|
-
})
|
|
415
|
-
|
|
416
|
-
const isContractToAddress = await isContractAddressCached({ asset, address: toAddress })
|
|
417
|
-
|
|
418
|
-
// HACK: We cannot ensure the no dust invariant for `isSendAll`
|
|
419
|
-
// transactions to contract addresses, since we may be
|
|
420
|
-
// performing a raw token transaction and the parameter
|
|
421
|
-
// applies to the token and not the native amount.
|
|
422
|
-
//
|
|
423
|
-
// Contracts have nondeterministic gas most of the time
|
|
424
|
-
// versus estimations, anyway.
|
|
425
|
-
const isSendAllBaseAsset = isSendAll && !isToken && !isContractToAddress
|
|
426
|
-
|
|
427
|
-
// For native send all transactions, we have to make sure that
|
|
428
|
-
// the `tipGasPrice` is equal to the `gasPrice`, since this is
|
|
429
|
-
// effectively like saying that the `maxFeePerGas` is equal
|
|
430
|
-
// to the `maxPriorityFeePerGas`. We do this so that for a
|
|
431
|
-
// fixed gas cost transaction, no dust balance should remain,
|
|
432
|
-
// since any deviation in the underlying `baseFeePerGas` will
|
|
433
|
-
// result only affect the tip for the miner - no dust remains.
|
|
434
|
-
if (eip1559Enabled && isSendAllBaseAsset) {
|
|
435
|
-
// force consuming all gas
|
|
436
|
-
tipGasPrice = gasPrice
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// HACK: If we are handling a send all transaction, we must ensure
|
|
440
|
-
// the send all invariant is maintained before producing the
|
|
441
|
-
// final transaction.
|
|
442
|
-
const maybeOverrideSendAllAmount =
|
|
443
|
-
isSendAllBaseAsset &&
|
|
444
|
-
(await HACK_maybeRefineSendAllAmount({
|
|
445
|
-
amount,
|
|
446
|
-
asset,
|
|
447
|
-
assetClientInterface,
|
|
448
|
-
walletAccount,
|
|
449
|
-
gasLimit,
|
|
450
|
-
gasPrice,
|
|
451
|
-
}))
|
|
452
|
-
|
|
453
|
-
if (maybeOverrideSendAllAmount) {
|
|
454
|
-
console.log(
|
|
455
|
-
`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 })}.`
|
|
456
|
-
)
|
|
457
|
-
amount = maybeOverrideSendAllAmount
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
const unsignedTx = await createUnsignedTx({
|
|
461
|
-
asset,
|
|
462
|
-
walletAccount,
|
|
463
|
-
address: toAddress,
|
|
464
|
-
amount,
|
|
465
|
-
nonce,
|
|
466
|
-
txInput,
|
|
467
|
-
gasLimit,
|
|
468
|
-
gasPrice,
|
|
469
|
-
tipGasPrice,
|
|
470
|
-
fromAddress,
|
|
471
|
-
eip1559Enabled,
|
|
472
|
-
})
|
|
473
|
-
|
|
474
|
-
// TODO: move into createUnsignedTx()
|
|
475
|
-
if (keepTxInput && !isToken) {
|
|
476
|
-
unsignedTx.txData.to = toAddress
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
unsignedTx.txMeta.eip1559Enabled = eip1559Enabled
|
|
480
|
-
|
|
481
|
-
const resolveFee = async () => {
|
|
482
|
-
if (providedFeeAmount) {
|
|
483
|
-
return providedFeeAmount
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
const optimismL1DataFee = asset.baseAsset.estimateL1DataFee
|
|
487
|
-
? await asset.baseAsset.estimateL1DataFee({
|
|
488
|
-
unsignedTx,
|
|
489
|
-
})
|
|
490
|
-
: undefined
|
|
491
|
-
|
|
492
|
-
const l1DataFee = optimismL1DataFee
|
|
493
|
-
? asset.baseAsset.currency.baseUnit(optimismL1DataFee)
|
|
494
|
-
: asset.baseAsset.currency.ZERO
|
|
495
|
-
return gasPrice.mul(gasLimit).add(l1DataFee)
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
const { txId, rawTx } = await assetClientInterface.signTransaction({
|
|
499
|
-
assetName: asset.baseAsset.name,
|
|
500
|
-
unsignedTx,
|
|
501
|
-
walletAccount,
|
|
502
|
-
})
|
|
503
|
-
|
|
504
|
-
return {
|
|
505
|
-
txId: normalizeTxId(txId),
|
|
506
|
-
rawTx,
|
|
507
|
-
nonce,
|
|
508
|
-
gasLimit,
|
|
509
|
-
gasPrice,
|
|
510
|
-
tipGasPrice,
|
|
511
|
-
feeAmount: await resolveFee(),
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
223
|
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
|