@exodus/ethereum-api 8.6.0 → 8.7.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 +9 -0
- package/package.json +2 -2
- package/src/gas-estimation.js +13 -12
- package/src/get-fee-async.js +31 -15
- package/src/nft-utils.js +53 -0
- package/src/tx-send/get-fee-info.js +0 -1
- package/src/tx-send/tx-send.js +17 -11
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [8.7.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.6.0...@exodus/ethereum-api@8.7.0) (2024-06-21)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* EVM NFT's fee and send ([#2626](https://github.com/ExodusMovement/assets/issues/2626)) ([2231954](https://github.com/ExodusMovement/assets/commit/22319543463e88046339ad6658683e13f0e71a46))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
6
15
|
## [8.6.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.5.0...@exodus/ethereum-api@8.6.0) (2024-06-18)
|
|
7
16
|
|
|
8
17
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.7.0",
|
|
4
4
|
"description": "Ethereum Api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -66,5 +66,5 @@
|
|
|
66
66
|
"type": "git",
|
|
67
67
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
68
68
|
},
|
|
69
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "685abde3787a364cdd00a675e95fe4dfa9053d96"
|
|
70
70
|
}
|
package/src/gas-estimation.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import BN from 'bn.js'
|
|
2
2
|
import * as ethUtil from '@exodus/ethereumjs-util'
|
|
3
|
-
import { currency2buffer,
|
|
3
|
+
import { currency2buffer, isEthereumLikeToken } from '@exodus/ethereum-lib'
|
|
4
4
|
import { estimateGas, isContractAddressCached } from './eth-like-util'
|
|
5
5
|
|
|
6
6
|
const EXTRA_PERCENTAGE = 20
|
|
@@ -42,7 +42,7 @@ export async function fetchGasLimit({
|
|
|
42
42
|
asset,
|
|
43
43
|
fromAddress,
|
|
44
44
|
toAddress,
|
|
45
|
-
txInput
|
|
45
|
+
txInput: providedTxInput,
|
|
46
46
|
amount,
|
|
47
47
|
bip70,
|
|
48
48
|
throwOnError = true,
|
|
@@ -53,21 +53,22 @@ export async function fetchGasLimit({
|
|
|
53
53
|
|
|
54
54
|
if (!amount) amount = asset.currency.ZERO
|
|
55
55
|
|
|
56
|
+
const isToken = isEthereumLikeToken(asset)
|
|
57
|
+
|
|
58
|
+
const txInput =
|
|
59
|
+
providedTxInput ||
|
|
60
|
+
(isToken
|
|
61
|
+
? ethUtil.bufferToHex(
|
|
62
|
+
asset.contract.transfer.build(toAddress.toLowerCase(), amount.toBaseString())
|
|
63
|
+
)
|
|
64
|
+
: '0x')
|
|
65
|
+
|
|
56
66
|
const defaultGasLimit = () =>
|
|
57
67
|
asset.gasLimit + GAS_PER_NON_ZERO_BYTE * ethUtil.toBuffer(txInput).length
|
|
58
68
|
|
|
59
|
-
const _isToken = isToken(asset)
|
|
60
|
-
|
|
61
69
|
const isContract = await isContractAddressCached({ asset, address: toAddress })
|
|
62
70
|
|
|
63
|
-
if (
|
|
64
|
-
// only create tx-input only if not pass tx-input to a token asset
|
|
65
|
-
if (txInput === '0x') {
|
|
66
|
-
txInput = ethUtil.bufferToHex(
|
|
67
|
-
asset.contract.transfer.build(toAddress.toLowerCase(), amount.toBaseString())
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
+
if (isToken) {
|
|
71
72
|
amount = asset.baseAsset.currency.ZERO
|
|
72
73
|
toAddress = asset.contract.address
|
|
73
74
|
} else if (
|
package/src/get-fee-async.js
CHANGED
|
@@ -2,6 +2,7 @@ import assert from 'minimalistic-assert'
|
|
|
2
2
|
import { fetchGasLimit } from './gas-estimation'
|
|
3
3
|
import { isForwarderContractCached } from './eth-like-util'
|
|
4
4
|
import { getFeeFactory } from './get-fee'
|
|
5
|
+
import { getNftArguments } from './nft-utils'
|
|
5
6
|
|
|
6
7
|
const FIXED_TRANSFER_GAS_LIMIT_ASSETS = new Set([
|
|
7
8
|
'amp',
|
|
@@ -33,38 +34,47 @@ const getFeeAsyncFactory = ({
|
|
|
33
34
|
assert(createUnsignedTx, 'createUnsignedTx is required')
|
|
34
35
|
const getFee = getFeeFactory({ gasLimit: defaultGasLimit })
|
|
35
36
|
return async ({
|
|
37
|
+
nft,
|
|
36
38
|
asset,
|
|
37
39
|
fromAddress = '0xffffffffffffffffffffffffffffffffffffffff', // sending from a random address
|
|
38
40
|
toAddress = '0xffffffffffffffffffffffffffffffffffffffff', // sending to a random address,
|
|
39
41
|
amount,
|
|
40
42
|
bip70,
|
|
41
|
-
txInput,
|
|
43
|
+
txInput: txInputPram,
|
|
42
44
|
isExchange,
|
|
43
45
|
customFee,
|
|
44
46
|
calculateEffectiveFee,
|
|
45
47
|
isSendAll,
|
|
46
48
|
isRbfAllowed = true, // Destkop, isRbfAllowed=true when advanced panel is on
|
|
47
|
-
throwOnError = false,
|
|
48
49
|
}) => {
|
|
49
|
-
const
|
|
50
|
+
const resolveGasLimit = async () => {
|
|
51
|
+
if (nft) {
|
|
52
|
+
return getNftArguments({ asset, nft, fromAddress, toAddress })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const extraPercentage = await resolveExtraPercentage({ asset, toAddress })
|
|
56
|
+
const txInput = txInputPram ?? '0x'
|
|
57
|
+
const gasLimit = await fetchGasLimit({
|
|
58
|
+
asset,
|
|
59
|
+
fromAddress,
|
|
60
|
+
toAddress,
|
|
61
|
+
txInput, // default value is '0x'
|
|
62
|
+
amount,
|
|
63
|
+
bip70,
|
|
64
|
+
extraPercentage,
|
|
65
|
+
})
|
|
66
|
+
return { gasLimit, txInput }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { txInput, gasLimit, contractAddress } = await resolveGasLimit()
|
|
50
70
|
|
|
51
|
-
const gasLimit = await fetchGasLimit({
|
|
52
|
-
asset,
|
|
53
|
-
fromAddress,
|
|
54
|
-
toAddress,
|
|
55
|
-
txInput: txInput ?? '0x', // default value is '0x'
|
|
56
|
-
amount,
|
|
57
|
-
bip70,
|
|
58
|
-
throwOnError,
|
|
59
|
-
extraPercentage,
|
|
60
|
-
})
|
|
61
71
|
const feeData = await assetClientInterface.getFeeConfig({ assetName: asset.baseAsset.name })
|
|
62
72
|
|
|
63
73
|
const optimismL1DataFee = asset.baseAsset.estimateL1DataFee
|
|
64
74
|
? await asset.baseAsset.estimateL1DataFee({
|
|
65
75
|
unsignedTx: createUnsignedTx({
|
|
66
76
|
asset,
|
|
67
|
-
address: toAddress,
|
|
77
|
+
address: contractAddress || toAddress,
|
|
68
78
|
fromAddress,
|
|
69
79
|
amount,
|
|
70
80
|
nonce: 0,
|
|
@@ -90,7 +100,13 @@ const getFeeAsyncFactory = ({
|
|
|
90
100
|
const l1DataFee = optimismL1DataFee
|
|
91
101
|
? asset.baseAsset.currency.baseUnit(optimismL1DataFee)
|
|
92
102
|
: asset.baseAsset.currency.ZERO
|
|
93
|
-
return {
|
|
103
|
+
return {
|
|
104
|
+
fee: fee.add(l1DataFee),
|
|
105
|
+
optimismL1DataFee,
|
|
106
|
+
gasLimit,
|
|
107
|
+
txInput,
|
|
108
|
+
...rest,
|
|
109
|
+
}
|
|
94
110
|
}
|
|
95
111
|
}
|
|
96
112
|
|
package/src/nft-utils.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import SolidityContract from '@exodus/solidity-contract'
|
|
2
|
+
import { fetchGasLimit } from './gas-estimation'
|
|
3
|
+
import assert from 'minimalistic-assert'
|
|
4
|
+
|
|
5
|
+
export const getNftArguments = async ({ asset, nft, fromAddress, toAddress }) => {
|
|
6
|
+
assert(asset, 'asset is required')
|
|
7
|
+
assert(nft, 'nft is required')
|
|
8
|
+
assert(fromAddress, 'fromAddress is required')
|
|
9
|
+
assert(toAddress, 'toAddress is required')
|
|
10
|
+
const { contractAddress, tokenId, contractType } = nft
|
|
11
|
+
|
|
12
|
+
const txInputs = []
|
|
13
|
+
|
|
14
|
+
if (!contractType || contractType === 'erc1155') {
|
|
15
|
+
const txInput = `0x${SolidityContract.simpleErc1155(contractAddress)
|
|
16
|
+
.safeTransferFrom.build(fromAddress, toAddress, tokenId, 1, [])
|
|
17
|
+
.toString('hex')}`
|
|
18
|
+
txInputs.push({ txInput, contractType: 'erc1155' })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!contractType || contractType === 'erc721') {
|
|
22
|
+
const txInput = `0x${SolidityContract.simpleErc721(contractAddress)
|
|
23
|
+
.safeTransferFrom.build(fromAddress, toAddress, tokenId)
|
|
24
|
+
.toString('hex')}`
|
|
25
|
+
txInputs.push({ txInput, contractType: 'erc721' })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const errors = [`Cannot fetch gas for ${asset.name}`]
|
|
29
|
+
const { txInput, gasLimit } = await Promise.any(
|
|
30
|
+
txInputs.map(async ({ txInput, contractType }) => {
|
|
31
|
+
try {
|
|
32
|
+
const gasLimit = await fetchGasLimit({
|
|
33
|
+
asset,
|
|
34
|
+
fromAddress,
|
|
35
|
+
toAddress: contractAddress,
|
|
36
|
+
txInput,
|
|
37
|
+
amount: asset.baseAsset.currency.ZERO,
|
|
38
|
+
})
|
|
39
|
+
return { txInput, gasLimit }
|
|
40
|
+
} catch (e) {
|
|
41
|
+
errors.push(`ContractType ${contractType} nft input ${txInput}. ${e.message}`)
|
|
42
|
+
throw e
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
).catch((e) => {
|
|
46
|
+
throw new Error(errors.join('\n'))
|
|
47
|
+
})
|
|
48
|
+
return {
|
|
49
|
+
contractAddress,
|
|
50
|
+
gasLimit,
|
|
51
|
+
txInput,
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/tx-send/tx-send.js
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
/* eslint-disable @exodus/export-default/last */
|
|
2
2
|
|
|
3
3
|
import { getNonce, transactionExists } from '../eth-like-util'
|
|
4
|
-
import {
|
|
5
|
-
calculateBumpedGasPrice,
|
|
6
|
-
isToken as checkIsToken,
|
|
7
|
-
normalizeTxId,
|
|
8
|
-
} from '@exodus/ethereum-lib'
|
|
4
|
+
import { calculateBumpedGasPrice, isEthereumLikeToken, normalizeTxId } from '@exodus/ethereum-lib'
|
|
9
5
|
import assert from 'minimalistic-assert'
|
|
10
6
|
import getFeeInfo from './get-fee-info'
|
|
7
|
+
import { getNftArguments } from '../nft-utils'
|
|
11
8
|
|
|
12
9
|
const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
|
|
13
10
|
assert(assetClientInterface, 'assetClientInterface is required')
|
|
14
11
|
assert(createUnsignedTx, 'createUnsignedTx is required')
|
|
15
12
|
return async ({
|
|
13
|
+
nft,
|
|
16
14
|
asset,
|
|
17
15
|
walletAccount,
|
|
18
16
|
amount,
|
|
@@ -43,13 +41,21 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
|
|
|
43
41
|
assetName: baseAsset.name,
|
|
44
42
|
walletAccount,
|
|
45
43
|
})
|
|
46
|
-
|
|
47
|
-
let nonceParam = _nonce
|
|
48
|
-
|
|
49
44
|
const feeData = await assetClientInterface.getFeeData({
|
|
50
45
|
assetName: baseAsset.name,
|
|
51
46
|
})
|
|
52
47
|
|
|
48
|
+
let contractAddress
|
|
49
|
+
if (nft) {
|
|
50
|
+
const nftArguments = await getNftArguments({ asset, nft, fromAddress, toAddress: address })
|
|
51
|
+
contractAddress = nftArguments.contractAddress
|
|
52
|
+
feeOpts.gasLimit = nftArguments.gasLimit
|
|
53
|
+
txInput = nftArguments.txInput
|
|
54
|
+
amount = asset.baseAsset.currency.ZERO
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let nonceParam = _nonce
|
|
58
|
+
|
|
53
59
|
let eip1559Enabled = feeData.eip1559Enabled
|
|
54
60
|
|
|
55
61
|
// `replacedTx` is always an ETH/ETC transaction (not a token)
|
|
@@ -102,7 +108,7 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
|
|
|
102
108
|
assetClientInterface,
|
|
103
109
|
asset,
|
|
104
110
|
walletAccount,
|
|
105
|
-
toAddress: address,
|
|
111
|
+
toAddress: contractAddress || address,
|
|
106
112
|
amount,
|
|
107
113
|
nonce: nonceParam,
|
|
108
114
|
fromAddress,
|
|
@@ -171,7 +177,7 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
|
|
|
171
177
|
],
|
|
172
178
|
})
|
|
173
179
|
|
|
174
|
-
const isToken =
|
|
180
|
+
const isToken = isEthereumLikeToken(asset)
|
|
175
181
|
if (isToken) {
|
|
176
182
|
await assetClientInterface.updateTxLogAndNotify({
|
|
177
183
|
assetName: baseAsset.name,
|
|
@@ -224,7 +230,7 @@ const createTx = async ({
|
|
|
224
230
|
feeOpts,
|
|
225
231
|
createUnsignedTx,
|
|
226
232
|
}) => {
|
|
227
|
-
const isToken =
|
|
233
|
+
const isToken = isEthereumLikeToken(asset)
|
|
228
234
|
|
|
229
235
|
if (txInput && isToken && !keepTxInput)
|
|
230
236
|
throw new Error(`Additional data for Ethereum Token (${asset.name}) is not allowed`)
|