@exodus/ethereum-api 8.5.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 CHANGED
@@ -3,6 +3,30 @@
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
+
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)
16
+
17
+
18
+ ### Features
19
+
20
+ * add tokenAssetType to base asset ([#2298](https://github.com/ExodusMovement/assets/issues/2298)) ([80c9dc8](https://github.com/ExodusMovement/assets/commit/80c9dc8a4d2a8614f84b66d2c9649cdf19601443))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * add handling 400 error code for eth like ([#2592](https://github.com/ExodusMovement/assets/issues/2592)) ([ac1121d](https://github.com/ExodusMovement/assets/commit/ac1121dab0cc451cfeeb8001915b11fc0bbe00a0))
26
+ * send fromAddress when approving tokens ([#2600](https://github.com/ExodusMovement/assets/issues/2600)) ([25a08ff](https://github.com/ExodusMovement/assets/commit/25a08ff08ca02405fc39d5590c60b025e98e703a))
27
+
28
+
29
+
6
30
  ## [8.5.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.4.2...@exodus/ethereum-api@8.5.0) (2024-06-17)
7
31
 
8
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.5.0",
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": "7f584775d566248fa9633086baee1064edbe8ef0"
69
+ "gitHead": "685abde3787a364cdd00a675e95fe4dfa9053d96"
70
70
  }
@@ -33,6 +33,7 @@ export const createApprove =
33
33
  spenderAddress,
34
34
  asset,
35
35
  feeData,
36
+ fromAddress,
36
37
  approveAmount,
37
38
  gasLimit,
38
39
  txInput: preTxInput,
@@ -50,6 +51,7 @@ export const createApprove =
50
51
  txInput,
51
52
  feeData,
52
53
  isContract: true,
54
+ fromAddress,
53
55
  }
54
56
 
55
57
  gasLimit = gasLimit || (await fetchGasLimit(gasLimitOptions))
@@ -64,6 +66,7 @@ export const createApprove =
64
66
  gasPrice: feeData.gasPrice,
65
67
  gasLimit,
66
68
  silent: true,
69
+ fromAddress,
67
70
  ...rest,
68
71
  })
69
72
 
@@ -58,7 +58,6 @@ export const createAssetFactory = ({
58
58
  nfts = false,
59
59
  serverUrl,
60
60
  stakingConfiguration = {},
61
- tokenType,
62
61
  useEip1191ChainIdChecksum = false,
63
62
  }) => {
64
63
  assert(assetsList, 'assetsList is required')
@@ -29,7 +29,7 @@ export function create(defaultURL, ensAssetName) {
29
29
  )
30
30
  } catch (err) {
31
31
  let nerr = err
32
- if (err.response && err.response.status === 500) {
32
+ if (err.response && (err.response.status === 500 || err.response.status === 400)) {
33
33
  try {
34
34
  const data = await err.response.json()
35
35
  const msg = data.error.replace(/^RPC error \(code: -\d+\): /, '')
@@ -1,6 +1,9 @@
1
1
  import {
2
+ // eslint-disable-next-line import/no-deprecated
2
3
  DEFAULT_SERVER_URLS,
4
+ // eslint-disable-next-line import/no-deprecated
3
5
  ETHEREUM_LIKE_ASSETS,
6
+ // eslint-disable-next-line import/no-deprecated
4
7
  ETHEREUM_LIKE_MONITOR_TYPES,
5
8
  } from '@exodus/ethereum-lib'
6
9
 
@@ -27,8 +30,11 @@ export function createEvmServer({ assetName, serverUrl, monitorType }) {
27
30
 
28
31
  // @Deprecated
29
32
  const serverMap = Object.fromEntries(
33
+ // eslint-disable-next-line import/no-deprecated
30
34
  ETHEREUM_LIKE_ASSETS.map((assetName) => {
35
+ // eslint-disable-next-line import/no-deprecated
31
36
  const monitorType = ETHEREUM_LIKE_MONITOR_TYPES[assetName]
37
+ // eslint-disable-next-line import/no-deprecated
32
38
  const serverUrl = DEFAULT_SERVER_URLS[assetName]
33
39
  return [assetName, createEvmServer({ assetName, serverUrl, monitorType })]
34
40
  })
@@ -1,6 +1,6 @@
1
1
  import BN from 'bn.js'
2
2
  import * as ethUtil from '@exodus/ethereumjs-util'
3
- import { currency2buffer, isToken } from '@exodus/ethereum-lib'
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 = '0x',
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 (_isToken) {
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 (
@@ -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 extraPercentage = await resolveExtraPercentage({ asset, toAddress })
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 { fee: fee.add(l1DataFee), optimismL1DataFee, gasLimit, ...rest }
103
+ return {
104
+ fee: fee.add(l1DataFee),
105
+ optimismL1DataFee,
106
+ gasLimit,
107
+ txInput,
108
+ ...rest,
109
+ }
94
110
  }
95
111
  }
96
112
 
@@ -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
+ }
@@ -11,10 +11,9 @@ export function createEthereumStakingService({
11
11
  asset,
12
12
  assetClientInterface,
13
13
  createAndBroadcastTX,
14
- getTelemetryId,
15
14
  }) {
16
15
  const staking = new EthereumStaking(asset)
17
- const stakingProvider = stakingProviderClientFactory({ getTelemetryId })
16
+ const stakingProvider = stakingProviderClientFactory()
18
17
 
19
18
  function amountToCurrency({ asset, amount }) {
20
19
  return isNumberUnit(amount) ? amount : asset.currency.parse(amount)
@@ -5,14 +5,10 @@ import { MaticStakingApi } from './api'
5
5
  import { stakingProviderClientFactory } from '../staking-provider-client'
6
6
  import { isNumberUnit } from '@exodus/currency'
7
7
 
8
- export function createPolygonStakingService({
9
- assetClientInterface,
10
- createAndBroadcastTX,
11
- getTelemetryId,
12
- }) {
8
+ export function createPolygonStakingService({ assetClientInterface, createAndBroadcastTX }) {
13
9
  const stakingApi = new MaticStakingApi()
14
10
  const assetName = 'ethereum'
15
- const stakingProvider = stakingProviderClientFactory({ getTelemetryId })
11
+ const stakingProvider = stakingProviderClientFactory()
16
12
 
17
13
  async function getStakeAssets() {
18
14
  const { polygon: asset, ethereum: feeAsset } = await assetClientInterface.getAssetsForNetwork({
@@ -6,7 +6,7 @@ const DEFAULT_STAKING_URL = 'https://staking.a.exodus.io'
6
6
  const HTTP_POST_TIMEOUT = ms('30s')
7
7
 
8
8
  export const stakingProviderClientFactory = (
9
- { defaultStakingUrl = DEFAULT_STAKING_URL, getTelemetryId } = Object.create(null)
9
+ { defaultStakingUrl = DEFAULT_STAKING_URL } = Object.create(null)
10
10
  ) => {
11
11
  assert(defaultStakingUrl, '"defaultStakingUrl" must be provided')
12
12
 
@@ -22,11 +22,8 @@ export const stakingProviderClientFactory = (
22
22
  }
23
23
 
24
24
  const stakingRequest = async ({ asset, data }) => {
25
- const headers = {}
26
- if (typeof getTelemetryId === 'function') headers.telemetryId = await getTelemetryId()
27
25
  return fetchival(stakingUrl, {
28
26
  timeout: HTTP_POST_TIMEOUT,
29
- headers,
30
27
  })(asset)('stake').post(data)
31
28
  }
32
29
 
@@ -1,4 +1,5 @@
1
1
  import { getServer } from '../exodus-eth-server'
2
+ // eslint-disable-next-line import/no-deprecated
2
3
  import { DEFAULT_SERVER_URLS } from '@exodus/ethereum-lib'
3
4
  import { Tx } from '@exodus/models'
4
5
 
@@ -31,6 +32,7 @@ export class EthereumNoHistoryMonitor extends BaseMonitor {
31
32
 
32
33
  setServer(config) {
33
34
  if (!config?.server) {
35
+ // eslint-disable-next-line import/no-deprecated
34
36
  this.server.setURI(DEFAULT_SERVER_URLS[this.asset.name])
35
37
  return
36
38
  }
@@ -28,7 +28,6 @@ const getFeeInfo = async function getFeeInfo({
28
28
  amount,
29
29
  txInput,
30
30
  throwOnError: false,
31
- isContract: feeOpts.isContract ?? undefined,
32
31
  })
33
32
  }
34
33
 
@@ -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 = checkIsToken(asset)
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 = checkIsToken(asset)
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`)