@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/src/tx-create.js DELETED
@@ -1,352 +0,0 @@
1
- import { calculateBumpedGasPrice, currency2buffer, isEthereumLikeToken } from '@exodus/ethereum-lib'
2
- import createEthereumJsTx from '@exodus/ethereum-lib/src/unsigned-tx/create-ethereumjs-tx.js'
3
- import assert from 'minimalistic-assert'
4
-
5
- import * as ErrorWrapper from './error-wrapper.js'
6
- import { isContractAddressCached } from './eth-like-util.js'
7
- import { ensureSaneEip1559GasPriceForTipGasPrice } from './fee-utils.js'
8
- import { ARBITRARY_ADDRESS, fetchGasLimit, resolveDefaultTxInput } from './gas-estimation.js'
9
- import { getFeeFactoryGasPrices } from './get-fee.js'
10
- import { getNftArguments } from './nft-utils.js'
11
- import { resolveNonce } from './tx-send/nonce-utils.js'
12
-
13
- async function createUnsignedTxWithFees({
14
- asset,
15
- chainId,
16
- to, // the tx to address, it could be the reciver address for native sending, the token contract, the DEX contract, etc
17
- value, // the value of the tx in NU, it can be the value in eth or 0 when calling contracts
18
- data, // the data of the tx in hex string, it can be 0x for native sending or the params when sending to a contract
19
- gasLimit,
20
- eip1559Enabled,
21
- gasPrice, // eip 1559: `maxFeePerGas`
22
- tipGasPrice, // eip 1559: `maxPriorityPerGas`
23
- nonce,
24
- bumpTxId,
25
- coinAmount, // coinAmount
26
- fromAddress, // user's sending address
27
- toAddress, // user's receiver address
28
- }) {
29
- assert(asset, 'asset is required')
30
- assert(typeof chainId === 'number', 'chainId is required')
31
- assert(to, 'to is required')
32
- assert(value, 'value is required')
33
- assert(data, 'data is required')
34
- assert(gasLimit, 'gasLimit is required')
35
- assert(gasPrice, 'gasPrice is required')
36
- assert(coinAmount, 'coinAmount is required')
37
- assert(fromAddress, 'fromAddress is required')
38
- assert(toAddress, 'toAddress is required')
39
- assert(typeof eip1559Enabled === 'boolean', 'eip1559Enabled is required')
40
-
41
- const ethjsTx = createEthereumJsTx({
42
- txData: {
43
- nonce,
44
- gasPrice: currency2buffer(gasPrice),
45
- tipGasPrice: tipGasPrice ? currency2buffer(tipGasPrice) : undefined,
46
- gasLimit,
47
- to,
48
- value: currency2buffer(value),
49
- data,
50
- chainId,
51
- },
52
- txMeta: {
53
- eip1559Enabled,
54
- },
55
- })
56
- const transactionBuffer = ethjsTx.serialize()
57
-
58
- const baseFee = gasPrice.mul(gasLimit)
59
- const optimismL1DataFee = asset.baseAsset.estimateL1DataFee
60
- ? await asset.baseAsset.estimateL1DataFee({
61
- unsignedTx: { txData: { transactionBuffer, chainId } },
62
- })
63
- : undefined
64
-
65
- const l1DataFee = optimismL1DataFee
66
- ? asset.baseAsset.currency.baseUnit(optimismL1DataFee)
67
- : asset.baseAsset.currency.ZERO
68
-
69
- const fee = baseFee.add(l1DataFee)
70
-
71
- const unsignedTx = {
72
- txData: { transactionBuffer, chainId },
73
- txMeta: {
74
- bumpTxId,
75
- eip1559Enabled,
76
- fromAddress,
77
- toAddress,
78
- amount: coinAmount.toDefaultString({ unit: true }),
79
- fee: fee.toDefaultString({ unit: true }),
80
- },
81
- }
82
- return { unsignedTx }
83
- }
84
-
85
- const createBumpUnsignedTx = async ({
86
- fromAddress,
87
- chainId,
88
- asset,
89
- bumpTxId,
90
- baseAssetTxLog,
91
- assetClientInterface,
92
- walletAccount,
93
- feeData,
94
- nonce: providedNonce,
95
- }) => {
96
- const baseAsset = asset.baseAsset
97
- const replacedTx = baseAssetTxLog.get(bumpTxId)
98
- const assets = await assetClientInterface.getAssetsForNetwork({ baseAssetName: baseAsset.name })
99
- if (!replacedTx || !replacedTx.pending) {
100
- throw new Error(`Cannot bump transaction ${bumpTxId}: not found or confirmed`)
101
- }
102
-
103
- let replacedTokenTx
104
- if (replacedTx.tokens.length > 0) {
105
- const [tokenAssetName] = replacedTx.tokens
106
- const tokenTxSet = await assetClientInterface.getTxLog({
107
- assetName: tokenAssetName,
108
- walletAccount,
109
- })
110
- replacedTokenTx = tokenTxSet.get(bumpTxId)
111
-
112
- if (replacedTokenTx) {
113
- // Attempt to overwrite the asset to reflect the fact that
114
- // we're performing a token transaction.
115
- asset = assets[tokenAssetName]
116
- if (!asset) {
117
- throw new Error(
118
- `unable to find ${tokenAssetName} during token bump transaction: asset was not available in assetsForNetwork`
119
- )
120
- }
121
- }
122
- }
123
-
124
- const toAddress = (replacedTokenTx || replacedTx).to
125
- const isToken = isEthereumLikeToken(asset)
126
- const txToAddress = isToken ? asset.contract.address : toAddress
127
- const coinAmount = (replacedTokenTx || replacedTx).coinAmount.negate()
128
- const gasLimit = replacedTx.data.gasLimit
129
-
130
- const value = isToken ? baseAsset.currency.ZERO : coinAmount
131
-
132
- const {
133
- gasPrice: currentGasPrice,
134
- baseFeePerGas: currentBaseFee,
135
- eip1559Enabled,
136
- tipGasPrice: currentTipGasPrice,
137
- } = feeData
138
- const { bumpedGasPrice, bumpedTipGasPrice } = calculateBumpedGasPrice({
139
- baseAsset,
140
- tx: replacedTx,
141
- currentGasPrice,
142
- currentBaseFee,
143
- currentTipGasPrice,
144
- eip1559Enabled,
145
- })
146
- const gasPrice = bumpedGasPrice
147
- const tipGasPrice = bumpedTipGasPrice
148
- const nonce = replacedTx.data.nonce
149
- const data = replacedTokenTx
150
- ? asset.contract.transfer.build(toAddress.toLowerCase(), coinAmount.toBaseString())
151
- : replacedTx.data.data || '0x'
152
-
153
- if (nonce === undefined) {
154
- throw new Error(`Cannot bump transaction ${bumpTxId}: data object seems to be corrupted`)
155
- }
156
-
157
- // If we have evaluated a bump transaction and the `providedNonce` differs
158
- // from the `bumpNonce`, we've encountered a conflict and cannot respect
159
- // the caller's request.
160
- if (typeof nonce === 'number' && typeof providedNonce === 'number' && nonce !== providedNonce)
161
- throw new ErrorWrapper.EthLikeError({
162
- message: new Error('incorrect nonce for replacement transaction'),
163
- reason: ErrorWrapper.reasons.bumpTxFailed,
164
- hint: 'providedNonce',
165
- })
166
-
167
- return createUnsignedTxWithFees({
168
- asset,
169
- chainId,
170
- to: txToAddress,
171
- value,
172
- data,
173
- gasLimit,
174
- gasPrice,
175
- tipGasPrice,
176
- nonce,
177
- bumpTxId,
178
- coinAmount,
179
- fromAddress,
180
- toAddress,
181
- eip1559Enabled,
182
- })
183
- }
184
-
185
- export const createTxFactory = ({ chainId, assetClientInterface, useAbsoluteNonce }) => {
186
- assert(assetClientInterface, 'assetClientInterface is required')
187
- assert(typeof chainId === 'number', 'chainId is required')
188
- return async ({
189
- asset,
190
- walletAccount,
191
- feeData,
192
- nft, // when sending nfts
193
- fromAddress: providedFromAddress, // wallet from address
194
- toAddress: providedToAddress, // user's to address, not the token or the dex contract
195
- contractAddress: providedContractAddress, // Provided when swapping a token via the DEX contract, not via the token's contract
196
- txInput: providedTxInput, // Provided when swapping via a DEX contract
197
- gasLimit: providedGasLimit, // Provided by exchange when known
198
- amount: providedAmount, // The NU amount to be sent, to be included in the tx value or tx input
199
- nonce: providedNonce,
200
- tipGasPrice: providedTipGasPrice,
201
- gasPrice: providedGasPrice,
202
- bip70,
203
- isExchange,
204
- customFee,
205
- isSendAll,
206
- bumpTxId,
207
- keepTxInput, // @deprecated this flag is used by swaps when swapping a token via DEX. The asset is token but the tx TO address is not the token address. Update swap to use `contractAddress`
208
- }) => {
209
- assert(asset, 'asset is required')
210
- assert(feeData, 'feeData is required')
211
- const fromAddress = providedFromAddress || ARBITRARY_ADDRESS
212
-
213
- const baseAssetTxLog = await assetClientInterface.getTxLog({
214
- assetName: asset.baseAsset.name,
215
- walletAccount,
216
- })
217
-
218
- if (bumpTxId) {
219
- return createBumpUnsignedTx({
220
- chainId,
221
- asset,
222
- fromAddress,
223
- bumpTxId,
224
- baseAssetTxLog,
225
- assetClientInterface,
226
- walletAccount,
227
- feeData,
228
- nonce: providedNonce,
229
- })
230
- }
231
-
232
- const toAddress = providedToAddress || ARBITRARY_ADDRESS
233
- const {
234
- gasPrice: maybeGasPrice,
235
- feeData: { tipGasPrice: maybeTipGasPrice, eip1559Enabled },
236
- } = getFeeFactoryGasPrices({ customFee, feeData })
237
-
238
- const isToken = isEthereumLikeToken(asset)
239
-
240
- const resolvedGasPrice = providedGasPrice ?? maybeGasPrice
241
-
242
- const txToAddress =
243
- providedContractAddress ?? (isToken && !keepTxInput ? asset.contract.address : toAddress)
244
-
245
- const isContractToAddress = await isContractAddressCached({ asset, address: txToAddress })
246
-
247
- // HACK: We cannot ensure the no dust invariant for `isSendAll`
248
- // transactions to contract addresses, since we may be
249
- // performing a raw token transaction and the parameter
250
- // applies to the token and not the native amount.
251
- //
252
- // Contracts have nondeterministic gas most of the time
253
- // versus estimations, anyway.
254
- const isSendAllBaseAsset = isSendAll && !isToken && !isContractToAddress
255
-
256
- // For native send all transactions, we have to make sure that
257
- // the `tipGasPrice` is equal to the `gasPrice`, since this is
258
- // effectively like saying that the `maxFeePerGas` is equal
259
- // to the `maxPriorityFeePerGas`. We do this so that for a
260
- // fixed gas cost transaction, no dust balance should remain,
261
- // since any deviation in the underlying `baseFeePerGas` will
262
- // result only affect the tip for the miner - no dust remains.
263
- const tipGasPrice =
264
- providedTipGasPrice ??
265
- (eip1559Enabled && isSendAllBaseAsset ? resolvedGasPrice : maybeTipGasPrice)
266
-
267
- const gasPrice = eip1559Enabled
268
- ? ensureSaneEip1559GasPriceForTipGasPrice({
269
- gasPrice: resolvedGasPrice,
270
- tipGasPrice,
271
- })
272
- : resolvedGasPrice
273
-
274
- const nonce =
275
- providedNonce ??
276
- (await resolveNonce({
277
- asset,
278
- fromAddress,
279
- txLog: baseAssetTxLog,
280
- // For assets where we'll fall back to querying the coin node, we
281
- // search for pending transactions. For base assets with history,
282
- // we'll fall back to the `TxLog` since this also has a knowledge
283
- // of which transactions are currently in pending.
284
- tag: 'pending',
285
- useAbsoluteNonce,
286
- }))
287
-
288
- if (nft) {
289
- const {
290
- contractAddress: txToAddress,
291
- gasLimit,
292
- txInput,
293
- } = await getNftArguments({
294
- asset,
295
- nft,
296
- fromAddress,
297
- toAddress: providedToAddress,
298
- })
299
-
300
- const value = asset.baseAsset.currency.ZERO
301
-
302
- return createUnsignedTxWithFees({
303
- chainId,
304
- asset,
305
- to: txToAddress,
306
- value,
307
- data: txInput,
308
- gasLimit,
309
- gasPrice,
310
- tipGasPrice,
311
- nonce,
312
- coinAmount: value,
313
- fromAddress,
314
- toAddress,
315
- eip1559Enabled,
316
- })
317
- }
318
-
319
- const amount = providedAmount ?? asset.currency.ZERO
320
-
321
- const value = isToken ? asset.baseAsset.currency.ZERO : amount
322
- const txInput = providedTxInput || resolveDefaultTxInput({ asset, toAddress, amount })
323
- const gasLimit =
324
- providedGasLimit ??
325
- (await fetchGasLimit({
326
- asset,
327
- feeData,
328
- fromAddress: providedFromAddress,
329
- toAddress: providedToAddress,
330
- txInput: providedTxInput,
331
- contractAddress: txToAddress,
332
- bip70,
333
- amount,
334
- }))
335
-
336
- return createUnsignedTxWithFees({
337
- asset,
338
- chainId,
339
- to: txToAddress,
340
- value,
341
- data: txInput,
342
- gasLimit,
343
- gasPrice,
344
- tipGasPrice,
345
- nonce,
346
- coinAmount: amount,
347
- fromAddress,
348
- toAddress,
349
- eip1559Enabled,
350
- })
351
- }
352
- }