@exodus/ethereum-api 8.41.0-alpha.0 → 8.42.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 +10 -0
- package/package.json +4 -3
- package/src/allowance/index.js +59 -30
- package/src/create-asset-utils.js +31 -0
- package/src/create-asset.js +7 -10
- package/src/gas-estimation.js +1 -2
- package/src/get-fee-async.js +81 -13
- package/src/get-fee.js +1 -1
- package/src/index.js +4 -1
- package/src/tx-send/get-fee-info.js +58 -0
- package/src/tx-send/index.js +1 -0
- package/src/tx-send/nonce-utils.js +1 -1
- package/src/tx-send/tx-send.js +369 -82
- package/src/tx-create.js +0 -352
package/src/tx-send/tx-send.js
CHANGED
|
@@ -1,92 +1,230 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @exodus/export-default/last */
|
|
2
|
+
|
|
3
|
+
import { calculateBumpedGasPrice, isEthereumLikeToken, normalizeTxId } from '@exodus/ethereum-lib'
|
|
2
4
|
import assert from 'minimalistic-assert'
|
|
3
5
|
|
|
4
6
|
import * as ErrorWrapper from '../error-wrapper.js'
|
|
5
|
-
import { transactionExists } from '../eth-like-util.js'
|
|
6
|
-
import {
|
|
7
|
+
import { isContractAddressCached, transactionExists } from '../eth-like-util.js'
|
|
8
|
+
import { getNftArguments } from '../nft-utils.js'
|
|
9
|
+
import getFeeInfo from './get-fee-info.js'
|
|
7
10
|
import { resolveNonce } from './nonce-utils.js'
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
12
33
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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')
|
|
19
55
|
|
|
20
|
-
|
|
56
|
+
const expectedSendAllAmount = spendable.sub(maxGasCost)
|
|
57
|
+
|
|
58
|
+
// If the client attempted to send the correct
|
|
59
|
+
// amount, good job! You get a cookie!
|
|
60
|
+
if (providedAmount.equals(expectedSendAllAmount)) return null
|
|
61
|
+
|
|
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
|
|
21
67
|
}
|
|
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
|
+
}
|
|
22
91
|
|
|
23
|
-
return async ({ asset, walletAccount, unsignedTx: providedUnsignedTx, ...legacyParams }) => {
|
|
24
92
|
const assetName = asset.name
|
|
25
93
|
const baseAsset = asset.baseAsset
|
|
26
94
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
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
102
|
|
|
32
|
-
|
|
33
|
-
legacyParams.feeData ??
|
|
34
|
-
(await assetClientInterface.getFeeData({
|
|
35
|
-
assetName: baseAsset.name,
|
|
36
|
-
}))
|
|
103
|
+
const { eip1559Enabled } = feeData
|
|
37
104
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
walletAccount,
|
|
43
|
-
}))
|
|
105
|
+
const fromAddress = await assetClientInterface.getReceiveAddress({
|
|
106
|
+
assetName: baseAsset.name,
|
|
107
|
+
walletAccount,
|
|
108
|
+
})
|
|
44
109
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
...legacyParams.options,
|
|
53
|
-
})
|
|
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
|
|
54
117
|
}
|
|
55
118
|
|
|
56
|
-
|
|
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)
|
|
57
130
|
|
|
58
|
-
|
|
59
|
-
|
|
131
|
+
if (!replacedTx || !replacedTx.pending) {
|
|
132
|
+
throw new Error(`Cannot bump transaction ${bumpTxId}: not found or confirmed`)
|
|
133
|
+
}
|
|
60
134
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
142
|
|
|
66
|
-
|
|
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
|
+
}
|
|
153
|
+
|
|
154
|
+
// TODO: Should we `throw` if we can't find the asset?
|
|
155
|
+
}
|
|
67
156
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const to = parsedTx.to
|
|
72
|
-
const eip1559Enabled = parsedTx.eip1559Enabled
|
|
157
|
+
address = (replacedTokenTx || replacedTx).to
|
|
158
|
+
amount = (replacedTokenTx || replacedTx).coinAmount.negate()
|
|
159
|
+
feeOpts.gasLimit = replacedTx.data.gasLimit
|
|
73
160
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
+
}
|
|
77
182
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
81
190
|
)
|
|
191
|
+
throw new ErrorWrapper.EthLikeError({
|
|
192
|
+
message: new Error('incorrect nonce for replacement transaction'),
|
|
193
|
+
reason: ErrorWrapper.reasons.bumpTxFailed,
|
|
194
|
+
hint: 'providedNonce',
|
|
195
|
+
})
|
|
82
196
|
|
|
83
|
-
|
|
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
|
+
|
|
209
|
+
const createTxParams = {
|
|
210
|
+
assetClientInterface,
|
|
84
211
|
asset,
|
|
85
|
-
unsignedTx,
|
|
86
212
|
walletAccount,
|
|
87
|
-
|
|
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
|
+
}
|
|
88
226
|
|
|
89
|
-
|
|
227
|
+
let { txId, rawTx, nonce, gasLimit, tipGasPrice, feeAmount } = await createTx(createTxParams)
|
|
90
228
|
|
|
91
229
|
if (isPrivate && typeof baseAsset.broadcastPrivateTx !== 'function')
|
|
92
230
|
throw new Error(
|
|
@@ -112,7 +250,7 @@ const txSendFactory = ({ assetClientInterface, createTx }) => {
|
|
|
112
250
|
reason: ErrorWrapper.reasons.insufficientFunds,
|
|
113
251
|
hint: 'broadcastTx',
|
|
114
252
|
})
|
|
115
|
-
} else if (
|
|
253
|
+
} else if (bumpTxId) {
|
|
116
254
|
throw new ErrorWrapper.EthLikeError({
|
|
117
255
|
message: err.message,
|
|
118
256
|
reason: ErrorWrapper.reasons.bumpTxFailed,
|
|
@@ -124,16 +262,18 @@ const txSendFactory = ({ assetClientInterface, createTx }) => {
|
|
|
124
262
|
reason: ErrorWrapper.reasons.broadcastTxFailed,
|
|
125
263
|
hint: 'otherErr:broadcastTx',
|
|
126
264
|
})
|
|
127
|
-
} else if (nonceTooLowErr && !
|
|
265
|
+
} else if (nonceTooLowErr && !isHardware) {
|
|
128
266
|
console.info('trying to send again...') // inject logger factory from platform
|
|
129
267
|
// let's try to fix the nonce issue
|
|
130
|
-
|
|
268
|
+
nonce = await resolveNonce({
|
|
131
269
|
asset,
|
|
270
|
+
fromAddress,
|
|
271
|
+
providedNonce,
|
|
272
|
+
txLog: baseAssetTxLog,
|
|
132
273
|
triedNonce: nonce,
|
|
133
274
|
forceFromNode: true,
|
|
134
275
|
})
|
|
135
|
-
|
|
136
|
-
;({ txId, rawTx } = await signTx({ asset, unsignedTx, walletAccount }))
|
|
276
|
+
;({ txId, rawTx } = await createTx({ ...createTxParams, nonce }))
|
|
137
277
|
|
|
138
278
|
try {
|
|
139
279
|
await baseAsset.api.broadcastTx(rawTx.toString('hex'))
|
|
@@ -156,18 +296,7 @@ const txSendFactory = ({ assetClientInterface, createTx }) => {
|
|
|
156
296
|
}
|
|
157
297
|
}
|
|
158
298
|
|
|
159
|
-
const
|
|
160
|
-
? {
|
|
161
|
-
gasLimit,
|
|
162
|
-
replacedTxId,
|
|
163
|
-
nonce,
|
|
164
|
-
...(tipGasPrice ? { tipGasPrice: tipGasPrice.toBaseString() } : Object.create(null)),
|
|
165
|
-
}
|
|
166
|
-
: {
|
|
167
|
-
gasLimit,
|
|
168
|
-
replacedTxId,
|
|
169
|
-
nonce,
|
|
170
|
-
}
|
|
299
|
+
const selfSend = fromAddress === address
|
|
171
300
|
|
|
172
301
|
await assetClientInterface.updateTxLogAndNotify({
|
|
173
302
|
assetName: asset.name,
|
|
@@ -181,12 +310,21 @@ const txSendFactory = ({ assetClientInterface, createTx }) => {
|
|
|
181
310
|
feeAmount,
|
|
182
311
|
feeCoinName: asset.feeAsset.name,
|
|
183
312
|
selfSend,
|
|
184
|
-
to,
|
|
313
|
+
to: address,
|
|
185
314
|
currencies: {
|
|
186
315
|
[assetName]: asset.currency,
|
|
187
316
|
[asset.feeAsset.name]: asset.feeAsset.currency,
|
|
188
317
|
},
|
|
189
|
-
data:
|
|
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 },
|
|
190
328
|
},
|
|
191
329
|
],
|
|
192
330
|
})
|
|
@@ -204,13 +342,20 @@ const txSendFactory = ({ assetClientInterface, createTx }) => {
|
|
|
204
342
|
feeAmount,
|
|
205
343
|
feeCoinName: baseAsset.name,
|
|
206
344
|
selfSend,
|
|
207
|
-
to,
|
|
345
|
+
to: address,
|
|
208
346
|
token: asset.name,
|
|
209
347
|
currencies: {
|
|
210
348
|
[baseAsset.name]: baseAsset.currency,
|
|
211
349
|
[asset.feeAsset.name]: asset.feeAsset.currency,
|
|
212
350
|
},
|
|
213
|
-
data:
|
|
351
|
+
data: eip1559Enabled
|
|
352
|
+
? {
|
|
353
|
+
gasLimit,
|
|
354
|
+
replacedTxId: bumpTxId,
|
|
355
|
+
nonce,
|
|
356
|
+
...(tipGasPrice ? { tipGasPrice: tipGasPrice.toBaseString() } : {}),
|
|
357
|
+
}
|
|
358
|
+
: { gasLimit, replacedTxId: bumpTxId, nonce },
|
|
214
359
|
},
|
|
215
360
|
],
|
|
216
361
|
})
|
|
@@ -220,4 +365,146 @@ const txSendFactory = ({ assetClientInterface, createTx }) => {
|
|
|
220
365
|
}
|
|
221
366
|
}
|
|
222
367
|
|
|
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
|
+
|
|
223
510
|
export default txSendFactory
|