@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-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
|
-
}
|